ansible установка

Thank you for reading this post, don't forget to subscribe!

Ansible — бес­плат­ное ПО для кон­фи­гу­ри­ро­ва­ния и управ­ле­ния ком­пью­те­ра­ми, соче­та­ет в себе раз­вер­ты­ва­ние мно­го­уз­ло­во­го про­грамм­но­го обес­пе­че­ния, выпол­не­ния раз­лич­ных задач и управ­ле­ние кон­фи­гу­ра­ци­я­ми. Он управ­ля­ет нода­ми (кото­рые долж­ны иметь под­держ­ку Python 2.4 или более позд­ней вер­сии) через SSH. Моду­ли рабо­та­ют над JSON и стан­дарт­ный вывод может быть напи­сан на любом язы­ке про­грам­ми­ро­ва­ния. Систе­ма исполь­зу­ет YAML для напи­са­нии функ­ций (задач выпол­не­ния). Глав­ная цель Ansible — это с одно­го (мож­но сде­лать и пару) сер­ве­ров с анси­б­лом мож­но было управ­лять все­ми дру­ги­ми нода­ми. С тако­го сер­ве­ра мож­но отправ­лять раз­лич­ные коман­ды (набор неко­то­рых инструк­ций, так назы­ва­е­мых playbooks) на любую из под­клю­чен­ных нод на сер­ве­ре. Выпол­не­ние команд выпол­ня­ет­ся путем под­клю­че­ния сер­ве­ра к ноде через пуб­лич­ный ключ по SSH.

В фай­ле «Host inventory» содер­жит­ся инфор­ма­ция о обслу­жи­ва­е­мых нодах ( где соб­ствен­но все коман­ды будут испол­не­ны). Кон­фи­гу­ра­ци­он­ный файл с Ansible может быть исполь­зо­ван для настрой­ки  пере­мен­но­го окружения.

В ansible, исполь­зу­ют­ся playbooks — это набор инструк­ций и они состо­ят из задач. Плей­бу­ки опи­сы­ва­ют­ся с помо­щью функ­ци­о­наль­но­сти моду­ля ядра само­го Ansible ( так же, могут исполь­зо­вать сто­рон­ние моду­ли). Playbooks — после­до­ва­тель­ность или набор команд в кото­рых  так же, может исполь­зо­вать­ся раз­лич­ные про­вер­ки: если усло­вие не выпол­ня­ет­ся, то опре­де­лен­ные коман­ды могут про­пус­кать­ся. Дан­ные кук­бу­ки, опи­сы­ва­ют­ся в фор­ма­те YAML.

Установка Ansible в CentOS/RedHat

Что­бы уста­но­вить ansible для нача­ла, сто­ит под­клю­чить репо­зи­то­рий EPEL

Уста­нав­ли­ва­ем ansible из пакета:

yum -y install ansible

Проверка версии ansible

Про­ве­ря­ем вер­сию ansible:

[root@centos64 ~]# ansible --version
ansible 2.5.5
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python2.6/site-packages/ansible
executable location = /usr/bin/ansible
python version = 2.6.6 (r266:84292, Aug 18 2016, 15:13:37) [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)]

 

Настройка Ansible в Unix/Linux

Сам кон­фи­гу­ра­ци­он­ный файл опи­сы­ва­ет­ся в фор­ма­те INI. Так же, име­ет­ся воз­мож­ность пере­опре­де­ле­ния части кон­фи­гу­ра­ции( мож­но даже все­го кон­фи­га) в пара­мет­рах playbook или в пере­мен­ных окружения.
При выпол­не­нии команд, Ansible выпол­ня­ет про­вер­ку и ищет нали­чия фай­ла с кон­фи­гу­ра­ци­я­ми в сле­ду­ю­щих расположениях:
Пара­метр ANSIBLE_CONFIG про­ве­ря­ет­ся пере­мен­ная окру­же­ния, кото­рый может быть ука­зы­вать на файл конфигурации.

  • ./ansible.cfg – в теку­щей папке.
  • ~/.ansible.cfg — в домаш­ней дирек­то­рии пользователя.
  • /etc/ansible/ansible.cfg — в пап­ке, смо­го ansible.

Настрой­ка через пере­мен­ные окружения

Мно­го опций, мож­но задать с помо­щью пере­мен­но­го окру­же­ния, для это­го сто­ит исполь­зо­вать пре­фикс ANSIBLE_ перед назва­ни­ем само­го пара­мет­ра с кон­фи­гу­ра­ци­ей (боль­ши­ми бук­ва­ми). Например:

export ANSIBLE_SUDO_USER=root

После чего пере­мен­ная ANSIBLE_SUDO_USER может быть исполь­зо­ва­на в playbook.

Настрой­ка в ansible.cfg

Самих пара­мет­ров в Ansible кон­фи­гу­ра­ции мно­го, но основ­ные я перечислю:

  • hostfile: Дан­ная опция ука­зы­ва­ет на путь к inventory file. В нем хра­нит­ся спи­сок ваших хостов, к кото­рым Ansible смо­жет выпол­нить подключение.
    Напри­мер: hostfile = /etc/ansible/hosts
  • library: Путь к пап­ке. В ней сохра­не­ны все моду­ли Ansible.
    Напри­мер: library = /usr/share/ansible
  • forks: Зада­ет­ся коли­че­ство про­цес­сов, кото­рые может поро­дить Ansible. По-умол­ча­нию пара­метр уста­нов­лен в 5.
    Напри­мер: forks = 5
  • sudo_user: Поль­зо­ва­тель кото­рый будет исполь­зо­ват­ся по умол­ча­нию и от кото­ро­го Ansible соб­ствен­но будет выпол­нять запуск команд на уда­лен­ных нодах.
    Напри­мер: sudo_user = root
  • remote_port: Порт для соеди­не­ния по SSH к нодам (по умол­ча­нию исполь­зу­ет стан­дарт­ный 22-й порт).
    Напри­мер: remote_port = 22
  • host_key_checking: Дан­ная опция  дает воз­мож­ность выклю­чить про­вер­ку SSH–ключа на хосте, но по-умол­ча­нию даная про­вер­ка включена.
    Напри­мер: host_key_checking = False
  • timeout: Дан­ная опция зада­ет тай­маут ( вре­мя попыт­ки под­клю­че­ния по SSH).
    Напри­мер: timeout = 60
  • log_path: Путь для сохра­не­ния лог-фай­лов. По-умол­ча­нию Ansible не сохра­нит их вообще.
    Напри­мер: log_path = /var/log/ansible.log

Пер­вый файл кон­фи­гу­ра­ции Ansible

Для это­го нуж­но выпол­нить под­клю­че­ние по SSH к сер­ве­ру где уста­нов­лен Ansible. Я создам пап­ку для всех моих про­ек­тов и после чего, перей­ду в нее

mkdir ~/ansible
cd ~/ansible

Так­же созда­ем дирек­то­рию для сохра­не­ния всех моду­лей для Ansible и так же, дирек­то­рию для сохра­не­ния всех логов

mkdir ~/ansible/modules
mkdir ~/ansible/logs

 

Созда­дим кон­фиг файл
nano ~/ansible/ansible.cfg

В него прописываем:

[defaults]
become_user = root
log_path = ~/ansible/logs/ansible.log
inventory = /etc/ansible/hosts

##hostfile = ~/ansible/inventory

 

В новой вер­сии ansible поля «hostfile» нет, за место его, есть другое:

inventory =  /etc/ansible/hosts

Ука­зы­ва­ем обслу­жи­ва­е­мые сер­ве­ра в host inventory

Как я опи­сы­вал рань­ше, дан­ный файл, слу­жит для вне­се­ния IP адре­сов всех име­ю­щих­ся нод ( те, кото­рые будут обслу­жи­вать­ся) и так же, сгруп­пи­рую их,  добав­ля­ем в /etc/ansible/hosts
[test1]
192.168.1.16

Гене­ра­ция клю­ча для ansible

ansible исполь­зу­ет под­клю­че­ние SSH по клю­чу (для досту­па к настра­и­ва­е­мым сер­ве­рам), по это­му, нуж­но сге­не­ри­ро­вать его:

ssh-keygen -t rsa -b 4096 -C "admin@centos"

Созда­ем про­стой ключ, все про­пус­ка­ем (нажи­ма­ем enter на все вопро­сы). После того как ключ сге­не­ри­ро­ва­ли, нуж­но его пере­дать на все име­ю­щий­ся ноды, для это­го есть команда:

ssh-copy-id root@ip_адрес_настраиваемого_сервера

у меня это:
ssh-copy-id root@192.168.1.16

И так для каж­дой управ­ля­е­мой ноды!

Мож­но попин­го­вать обслу­жи­ва­е­мые сервера:

ansible test1 -m ping
192.168.1.16 | SUCCESS => {
"changed": false,
"ping": "pong"
}

 

Мож­но пин­га­нуть и про­ве­рить все сервера

# ansible -m ping all

Мож­но так же, выпол­нить пинг по груп­пи­ро­ван­ной группе:
# ansible -m ping test1
Мож­но бы так­же ука­зать инди­ви­ду­аль­ный хост:
# ansible -m ping some_your_host1
Опция «-m ping» — часть коман­ды для ansible кото­рая дает воз­мож­ность исполь­зо­вать модуль «Ping». Вы може­те про­ве­рить рабо­ту дан­ной коман­ды с опций «-a» и про­ве­рить как рабо­та­ет дан­ная опция.

Модуль «shell, command» поз­во­ля­ет нам послать коман­ду на уда­лен­ный хост и полу­чить резуль­та­ты. Напри­мер, что­бы узнать исполь­зо­ва­ние памя­ти на нашей машине some_your_host_1, мы мог­ли бы использовать:

ansible -m command -a 'df -h' test1
192.168.1.16 | SUCCESS | rc=0 >>
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root
6.5G 2.3G 3.9G 37% /
tmpfs 250M 0 250M 0% /dev/shm
/dev/sda1 477M 26M 427M 6% /boot

 

Управление ansible конфигурациями

Рабо­та с playbook-ами

На playbook-ах осно­ван соб­ствен­но весь ansible, так как они слу­жат ( содер­жат) для выпол­не­ния раз­ных задач. Каж­дая напи­сан­ная зада­ча внут­ри само­го Ansible исполь­зу­ет кусок кода-моду­ля. Плей­бу­ки, исполь­зу­ют фор­мат YAML, но соб­ствен­но, любые дру­гие ваши моду­ли могут быть напи­са­ны на любом язы­ке программирования.

Хочу заме­тить, что очень важ­но, что­бы фор­мат сооб­ще­ний от моду­лей был в JSON.

YAML

Каж­дый плей­бук пишет­ся на язы­ке YAML и для анси­бл, почти каж­дый YAML файл начи­на­ет­ся с неко­то­ро­го спис­ка, а этот эле­мент дан­но­го спис­ка — спи­сок пар «ключ-значение»(иногда зовут словарем).

Дан­ные фай­лы обя­за­ны начи­нать­ся с «—» (такой фор­мат при­су­щий YAML )и озна­ча­ет нача­ло ваше­го документа.

Так же, все ваши спис­ки долж­ны нахо­дит­ся с оди­на­ко­вым отсту­пом от нача­ла каж­дой стро­ки, и долж­ны начи­нать­ся с про­бе­ла или «-«. Что­бы заком­мен­ти­ро­вать, используйте «#».
Например:

#Message
- Administration
– Install

Сло­варь пред­став­лен в виде «ключ:» (двое­то­чие и про­бел) «зна­че­ние»:

#Message
site: sid.com1
blog: blog.linux-notes

При необ­хо­ди­мо­сти сло­ва­ри могут быть пред­став­ле­ны в сокра­щен­ной форме:

#Comment
{site: linux-notes, blog: blog.linux-notes}

Мож­но ука­зать логи­че­ские зна­че­ние (истина/ложь) так:

need_access: no
use_service: yes
file_conf: TRUE
read_value: True
kill_process: false

Цели­ком наш при­мер YAML–файла будет выгля­деть так:

#About blog
site: sid.com1
blog: blog
must_read: True
themes:
- hosting
- cloud
- it
- geeks
brands:
- infobox
- infoboxcloud

Для пере­мен­ных Ansible исполь­зу­ет «{{ var }}». Если зна­че­ние после двое­то­чия начи­на­ет­ся с «{«, то YAML будет думать, что это словать.
Для исполь­зо­ва­ния пере­мен­ных нуж­но заклю­чить скоб­ки в кавычки:

word: "{{ variable }}"

 

 первый playbook

Playbooks может состо­ять из спис­ка обслу­жи­ва­е­мых сер­ве­ров, пере­мен­ных поль­зо­ва­те­ля, задач, обра­бот­чи­ков (хенд­ле­ров) и т.д. Боль­шин­ство настро­ек кон­фи­гу­ра­ции мож­но пере­опре­де­лить в playbook. Каж­дый playbook состо­ит из одно­го или более дей­ствия (игры) в списке.

Цель игры — свя­зать груп­пу хостов с пред­опре­де­лен­ны­ми роля­ми, пред­став­лен­ны­ми как вызов задач Ansible.

В каче­стве при­ме­ра давай­те рас­смот­рим про­цесс уста­нов­ки nginx.
Созда­дим дирек­то­рию, где будут хра­нит­ся playbooks:

mkdir ~/ansible/playbooks

Созда­дим файл setup_nginx.yml в дирек­то­рии playbooks:

vim ~/ansible/playbooks/setup_nginx.yml

со сле­ду­ю­щим содержанием:

- hosts: test1
tasks:

- name: Install nginx package
apt: name=nginx update_cache=yes
sudo: yes

- name: Starting nginx service
service: name=nginx state=started
sudo: yes

Давай­те рас­смот­рим содержимое:

  • hosts: Спи­сок хостов или груп­па, на кото­рой вы запус­ка­е­те зада­чу. Это поле обя­за­тель­ное и каж­дый playbook дол­жен иметь его, за исклю­че­ни­ем ролей. Если ука­за­на хост-груп­па, сна­ча­ла Ansible ее ищет в playbook, а затем в фай­ле inventory. Узнать, на каких хостах будет про­ис­хо­дить рабо­та, мож­но коман­дой: ansible-playbook —list-host, где – путь к ваше­му playbook (playbooks/setup_nginx.yml).
  • tasks: Зада­чи. Все playbooks содер­жат зада­чи. Зада­ча — это спи­сок дей­ствий, кото­рые вы хоти­те выпол­нить. Поле зада­чи содер­жит имя зада­чи (спра­воч­ная инфор­ма­ция о зада­че для поль­зо­ва­те­ля playbook), модуль, кото­рый дол­жен быть выпол­нен и аргу­мен­ты, тре­бу­е­мые для моду­ля. Пара­метр «name» опци­о­наль­ный, но рекомендуемый.

Коман­ды

playbook для ansible
Выпол­не­ние плей­бу­ка с назва­ни­ем test_job.yml для опре­де­лен­но­го хоста или груп­пы хостов (про­пи­са­но в файле):

ansible-playbook --check -i my_custom_hosts_file test_job.yml

Опция «-i» гово­рит анси­б­лу исполь­зо­вать про­из­воль­ные хосты с фай­ла. Ино­гда вы може­те захо­теть выпол­нить дан­ный playbook (или часть его), толь­ко на опре­де­лен­ных хостах. Вме­сто того, что­бы делать раз­лич­ные хосты в раз­ные фай­лы, мож­но попро­бо­вать эти методы:

Исполь­зуй­те опцию «—limit» что­бы ука­зать на каком хосте запу­стить ука­зан­ный плей­бук (напри­мер на host1):

ansible-playbook -i all_hosts.inventory some_playbook.yml --limit "your_host_1"

Исполь­зуй­те опцию «—limit» что­бы ука­зать несколь­ко хостов (роз­де­лен­ные запя­ты­ми), напри­мер для  host1,host2,host3:

ansible-playbook -i all_hosts.inventory some_playbook.yml --limit "your_host_1,your_host_2,your_host_3"

Исполь­зуй­те опцию «—limit» что­бы ука­зать диа­па­зон подоб­ных хостов, напри­мер host10-host20 или host5-15:

# ansible-playbook -i all_hosts.inventory some_playbook.yml --limit your_host[10-20]
# ansible-playbook -i all_hosts.inventory some_playbook.yml --limit your_host[5-15]

Исполь­зуй­те опцию «—limit» что­бы ука­зать файл с кото­ро­го будет брать­ся хосты (долж­ны идти в столбик):

# ansible-playbook -i all_hosts.inventory some_playbook.yml --limit @/path_to_file

Если вы не уве­ре­ны в том, что опция «—limit» будет рабо­тать, вы може­те исполь­зо­вать опцию «—list-hosts» и вы полу­чи­те спи­сок хостов, на кото­рых будет выпол­нять­ся playbook.

Мож­но зада­вать так зва­ные мет­ки для неко­то­рых задач и потом выпол­нять их по этим задан­ным мет­кам. Мож­но сде­лать зада­чу что­бы она выпол­ня­лась в любых усло­ви­ях, неза­ви­си­мо от меток. Это может быть сде­ла­но путем добавления:

always_run: yes

При­ме­ры

При­мер исполь­зо­ва­ния  «—tags» и «—skip-tags», откры­ва­ем  main.yml файл и прописываем:

tasks:

- name: Copy MY_FILE_HERE_1
copy: src=/root/work/MY_FILE_HERE_1 dest=/root/work/MY_FILE_HERE_1
tags:
- MY_FILE_HERE_1

- name: Copy MY_FILE_HERE_2
copy: src=/root/work/MY_FILE_HERE_2 dest=/root/work/MY_FILE_HERE_2
tags:
- MY_FILE_HERE_2

- name: Copy MY_FILE_HERE_2
copy: src=/root/work/MY_FILE_HERE_3 dest=/root/work/MY_FILE_HERE_3
tags:
- MY_FILE_HERE_3

 

Теперь, если хоти­те ско­пи­ры­вать ТОЛЬКО MY_FILE_HERE_1, выполните:

# ansible-playbook main.yml --tags "MY_FILE_HERE_1"

Если хоти­те наобо­рот, ско­пи­ро­вать все, НО КРОМЕ MY_FILE_HERE_1, запустите:

# ansible-playbook main.yml --skip-tags "MY_FILE_HERE_1"

Про­верь­те, какие зада­чи будут выпол­не­ны из дан­но­го плей­бу­ка «—tags или —skip-tags»:

# ansible-playbook main.yml --skip-tags "MY_FILE_HERE_1" --list-tags
# ansible-playbook main.yml --tags "MY_FILE_HERE_1" --list-tags

 

При­мер хэш-цикла:

- name: vim files
synchronize:
src=/absolute/repo/path/roles/common/files/{{ item.src }}
dest={{ item.dst }}
with_items:
- {src: vim/bundle, dst: /home/jefdaj/.vim }
- {src: vim/vimrc , dst: /home/jefdaj/.vimrc}
- {src: vim/bundle, dst: /root/.vim }
- {src: vim/vimrc , dst: /root/.vimrc }

- name: vim permissions
file: path={{ item.pth }} owner={{ item.own }} group={{ item.grp }} recurse=yes
with_items:
- {pth: /root/.vim , own: root , grp: root }
- {pth: /root/.vimrc , own: root , grp: root }
- {pth: /home/jefdaj/.vim , own: jefdaj, grp: jefdaj}
- {pth: /home/jefdaj/.vimrc, own: jefdaj, grp: jefdaj}

Выпол­нить любую команду:

# ansible -i my_inventory all -a "/bin/uptime" -f 10

Запус­ка­ем коман­ду  «-a «/bin/uptime»»  на всех хостах ( опция «all») с опре­де­лен­но­го фай­ла (опция «-i») с уста­нов­лен­ным пара­лель­ным исполь­зо­ва­ни­ем в 10.

Запус­ка­ем коман­ды с исполь­зо­ва­ни­ем моду­ля «shell»:

# ansible -i my_inventory some_host -m shell -a 'echo $TERM'

Где, some_host — Хост, на кото­ром будет выпол­нять­ся команда.

 

Копи­ру­ем файл на уда­лен­ный сервер

$ ansible -i my_hosts -m copy -a "src=/my_local_path_file dest=my_destination_path_file"

Мы же созда­дим чистую роль:

# ansible-galaxy init jenkins-installation -p roles

PS: У меня запус­ка­ет­ся с теку­щей дирек­то­рии ( где нахо­дит­ся про­ект с ансиблом).

 

Замена строк/слов в файле через ansible

При рабо­те с ansible и фай­ла­ми (кон­фи­гу­ра­ци­он­ные фай­лы — как при­мер), очень про­сто най­ти и заме­нить нуж­ную стро­ку или сло­во чем ско­пи­ро­вать файл на ком­пью­тер и редак­ти­ро­вать его.  В анси­б­ле, име­ет­ся модуль lineinfile, кото­рый предо­став­ля­ет функ­ци­о­наль­ные воз­мож­но­сти напо­до­бие «sed». Конеч­но, мож­но исполь­зо­вать и ути­ли­ту sed, но если нуж­но заме­нить на раз­ных сер­ве­рах одну и туже стро­ку во мно­гих фай­лах — то для этой зада­чи хоро­шо подой­дет ansible.

Это мож­но это сде­лать с помо­щью playbook-а:

- name: uncomment a line
lineinfile: dest=/etc/lighttpd/lighttpd.conf
regexp='^ include "mod_fastc gi.conf"'
insertafter='^# include "mod_fastc gi.conf"'
line=' include "mod_fastc gi.conf"'
state=present

Ниже пред­став­ля­ет собой про­стой набор раз­лич­ных вари­ан­тов исполь­зо­ва­ния моду­ля lineinfile.

Созда­дим тесто­вый файл через ансибль для тести­ро­ва­ния рабо­ты lineinfile модуля:

- name: create a complete empty file
command: /usr/bin/touch /home/vagrant/tmp.txt

 

Что­бы создать новую, пустую стро­ку — используем:

- name: create a new file with lineinfile
lineinfile: dest=/home/vagrant/tmp.txt
regexp='^' line=''
state=present
create=True

Дан­ный плей­бук, создаст пустую стро­ку. Сей­час, я пока­жу как мож­но ее заме­нить на что-то другое:

- name: add a string to the new file
lineinfile: dest=/home/vagrant/tmp.txt
regexp='^'
line='This works'
state=present

 

Доба­вим 5 строк в файл:

- name: add a multiline string to the file and delete the string from before
lineinfile: dest=/home/vagrant/tmp.txt
regexp='^'
line='#This is a 1st comment\n#The 2d comment\n#The 3d comment\n#The 4th a comment\n#The last comment'
state=present

 

Полу­ча­ем:

$ cat tmp.txt

#This is a 1st comment
#The 2d comment
#The 3d comment
#The 4th a comment
#The last comment

 

Вста­вить стро­ку, после задан­ной ( напри­мер после 1-й):

- name: add a single line, in this case the same as the comment but uncommented
lineinfile: dest=/home/vagrant/tmp.txt
regexp='^This'
insertafter='^#This'
line='This is a 1st comment'
state=present

Полу­ча­ем:

$ cat tmp.txt

#This is a 1st comment
This is a 1st comment
#The 2d comment
#The 3d comment
#The 4th a comment
#The last comment

 

Уда­ля­ем опре­де­лен­ную строку:

- name: remove the line '#This is a 1st comment'
lineinfile: dest=/home/vagrant/tmp.txt
regexp='^#This is a 1st comment'
state=absent

Вывод:

$ cat tmp.txt

This is a 1st comment
#The 2d comment
#The 3d comment
#The 4th a comment
#The last comment

 

Заме­нить стро­ку ( в моем слу­чае — это первая):

- name: change a str
lineinfile: dest=/home/vagrant/tmp.txt
regexp='^This'
insertbefore=BOF
line='This is no longer a comment'

Вот что вышло:

$ cat tmp.txt

This is no longer a comment
#The 2d comment
#The 3d comment
#The 4th a comment
#The last comment

 

Вста­вить стро­ку перед указанной:

- name: add a new string before the match
lineinfile: dest=/home/vagrant/tmp.txt
regexp='^The 3d comment'
insertbefore='^#The 3d comment'
line='Another The 3d comment'

вывод

$ cat tmp.txt

This is no longer a comment
#The 2d comment
Another The 3d comment
#The 3d comment
#The 4th a comment
#The last comment

 

Встав­ля­ем стро­ку в самый конец файла:

- name: add a new string at the end of the file
lineinfile: dest=/home/vagrant/tmp.txt
regexp=''
insertafter=EOF
line='The latest entry'

полу­ча­ем:

$ cat tmp.txt

This is no longer a comment
#The 2d comment
Another The 3d comment
#The 3d comment
#The 4th a comment
The latest entry