Konfigürasyon yönetimi ile ilgili konularda adı geçen yazılımlar genellikle Salt, Chef, Puppet ve Ansible’dır. Bu yazılımlar arasında Puppet ve Ansible’ı direkt kullanma ve tecrübe etme şansı yakaladım. Diğer ikisi ise henüz deneyimlediğim araçlar değil. Bu yazı Ansible’dan bahsedip üzerine bir kaç örnek vermek istiyorum.

Ansible Linux, Windows, fiziksel cihaz fark etmeksizin ip üzerinden parola ile erişim sağlanabilen hemen her yazılım/cihazda kullanılan bir konfigürasyon yönetim aracıdır. Çalışma sistemi olarak bir client veya agent’a ihtiyaç duymaması diğer konfigürasyon yönetim araçları ile aralarındaki en büyük farktı. Bu sebeple diğer araçlara göre daha esnek, kolay öğrenilebilir ve geliştirilebilir yapıdadır. Paket yöneticisine ansible tanımı verildiğinde kurulum sağlanacaktır.

## Debian Based
$ sudo apt install ansible

## RHEL Based
$ sudo yum install ansible

Ansible’ı kullanabilmemiz için önemli olan iki adet dosya vardır. Birisi ansible.cfg adındaki Ansible ile ilgili konfigürasyonların bulunduğu dosyadır. Diğeri ise yöneteceğimiz cihazlarla ilgili ip ve kullanıcı bilgilerinin tutulduğu hosts dosyasıdır. Bu dosyalar /etc/ansible dizini altında tutulur.

├── ansible
│   ├── ansible.cfg
│   └── hosts

$ wc -l /etc/ansible/ansible.cfg
490 /etc/ansible/ansible.cfg

Bu dosyadaki satırları tek tek açıklamak gibi bir isteğim olsa da satır sayısı görebildiğiniz üzere çok fazla ve zaten açtığınızda içerisinde komutlarla ilgili açıklama satırlarını görebilirsiniz. Burada bulunan tanımlar varsayılan olarak kullanılan tanımlardır. Eğer birini değiştirmek isterseniz başındaki # karakterini kaldırıp değiştirdiğinizde sonraki ansible işleminde sizin yeni tanımınıza göre çalışacaktır. Hosts dosyası ile başta boş gelir. İçerisine siz ekleme yaptıkça kullanılabilir hale gelmektedir.

Ansible varsayılan olarak /etc/ansible dizinine baksa da eğer Ansible komutunun yürütüldüğü dizinde ansible.cfg ve bir hosts dosyası varsa ayarları bulunduğu dizindeki konfigürasyona göre işlem yapacaktır. Şimdi bu durumu bir örnek üzerinden anlatmaya çalışacağım. Bu sırada kullanacağım sunucular:

IP Hostname OS
192.168.188.18 ansible Ubuntu 20.04
192.168.188.19 nfs Rocky Linux 8
192.168.188.20 kube20 Ubuntu 20.04
192.168.188.21 kube21 Ubuntu 20.04
192.168.188.22 kube22 Ubuntu 20.04

İlk aşama olarak erişilebilirlik ayarlarını yapalım. Yukarıda da bahsettiğimiz gibi Ansible ajan olmaksızın sadece parola ile kullanılabilen bir yazılım. Bunun için ssh-key üretip karşı tarafa kopyalamamız gerekiyor. Öncelikle bir ssh-key üretelim.

sergen@ansible:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/sergen/.ssh/id_rsa):
Created directory '/home/sergen/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/sergen/.ssh/id_rsa
Your public key has been saved in /home/sergen/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:XHuSM/shTRmkL8UAoiXhzUzRM2JXp17Z1yqz0UCwfSQ sergen@ansible
The key's randomart image is:
+---[RSA 3072]----+
|    o.=o.o+.E .  |
|   . Oo.=  @ =  .|
|    o.+o o= O o o|
|       . o * * o |
|        S O O o  |
|           X =   |
|          o +    |
|           o .   |
|            .    |
+----[SHA256]-----+

Sonrasında ise ssh-key’lerimi diğer sunuculara gönderiyorum. Burada bir standart olarak ansible kullanırken ansible’a özel bir kullanıcı oluşturulmasını ve sadece o kullanıcının kullanılmasını öneriyorum.

sergen@ansible:~$ ssh-copy-id ansible@192.168.188.19
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/sergen/.ssh/id_rsa.pub"
The authenticity of host '192.168.188.19 (192.168.188.19)' can't be established.
ECDSA key fingerprint is SHA256:FeyOY5bziF7gWRA4csD/Mo9Ex0jguYaA9EXBj4jcvU0.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
ansible@192.168.188.19's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'ansible@192.168.188.19'"
and check to make sure that only the key(s) you wanted were added.

sergen@ansible:~$ ssh-copy-id ansible@192.168.188.20
sergen@ansible:~$ ssh-copy-id ansible@192.168.188.21
sergen@ansible:~$ ssh-copy-id ansible@192.168.188.22

Şimdi tüm ssh-key’leri gönderdikten sonra konfigürasyon dizin ve dosyalarını oluşturabiliriz.

sergen@ansible:~$ mkdir test-area
sergen@ansible:~$ touch test-area/ansible.cfg
sergen@ansible:~$ touch test-area/envanter

Şimdi bu dosyalardan ansible.cfg olanın içerisinde sadece envanter dosyasına tanım yapacağım. Bu sayede test-area dizinine girdiğimde sadece envanter dizinindeki hostları göreceğiz.

sergen@ansible:~$ cat test-area/ansible.cfg
[defaults]
inventory       = ./envanter

Bunun için öncelikle ansible.cfg dosyasına yukarıdaki şekilde tanım yapıyoruz. Burada yapacağımız diğer işlem ise envanter dosyasına tanım yapmak. Başka bir şey yapmadığımız için diğer her şey varsayılan olarak çalışacaktır.

sergen@ansible:~$ cat test-area/envanter
[storage]
nfs  ansible_host=192.168.188.19  ansible_user=ansible

[kubernetes]
kube20  ansible_host=192.168.188.20  ansible_user=ansible
kube21  ansible_host=192.168.188.21  ansible_user=ansible
kube22  ansible_host=192.168.188.22  ansible_user=ansible

Dosyanın içeriğinden bahsetmek gerekirse köşeli parantez içerisindeki kısımlar playbook ve düz komutları yürütürken çağıracağımız genel isimlerdir. Bunların altında bulunan satırda ise öncelikle host için kullanacağımız isim sonrasında bu hostun ip bilgisi ve en son kullanıcı bilgisini ekliyoruz. Şimdi bunu yaptığımıza göre bir ansible komutu yürütelim ve sunuculara erişim kontrolü yapmak için ping atalım.

sergen@ansible:~$ ansible all -m ping
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not
match 'all'

Komutu yürüttüğümüzde /etc/ansible dizini altındaki hosts dosyasına baktığı için bir host bulamıyor.

sergen@ansible:~/test-area$ ansible all -m ping
nfs | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
kube21 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
kube22 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
kube20 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

test-area dizini içerisine girdiğimizde ise komut ilk olarak o dizindeki ansible.cfg dosyasını kontrol ediyor ve envanter dosyasındaki hostları tek tek kontrol ediyor. Yukarıda ping modülünü kullanarak işlem yaptık ancak burada kullanabileceğimiz pek çok modül bulunmaktadır. Bunlardan başka bir tanesi de command’dır. Bu modül ile komut satırında komut yürütüyormuş gibi toplu komut çalıştırabiliriz.

sergen@ansible:~/test-area$ ansible all -m command -a "date"
nfs | CHANGED | rc=0 >>
Wed May  4 13:46:45 EDT 2022
kube20 | CHANGED | rc=0 >>
Wed 04 May 2022 06:04:49 PM UTC
kube21 | CHANGED | rc=0 >>
Wed 04 May 2022 06:04:50 PM UTC
kube22 | CHANGED | rc=0 >>
Wed 04 May 2022 06:04:50 PM UTC

Şimdi başka bir örnek yapalım ve sunucuların dağıtımlarını öğrenelim. Bunun için ise Ansible Playbook yapısını kullanacağız. Zaten yukarıdaki komut örneği basit şeyler için kullanılır ve Ansible için asıl kullanılan yapı Playbook’lardır.

sergen@ansible:~/test-area$ cat distribution.yaml
---
- hosts: all
  gather_facts: yes
  become: false
  tasks:
  - name: Dağıtım
    debug: msg=""
  - name: Dağıtım versiyonu
    debug: msg=""

Bunun için yukarıdaki dosyayı kullanacağız. Yapısından bahsetmek gerekise yaml formatı kullanıyoruz. hosts kısmında envanter dosyasındaki köşeli parantez içerisinde yazanları yazabiliriz. Bu sayede sadece o bölüm altındaki hostlarda işlem yapmış oluruz.

İşletim sisteminden temel verileri çekmek için gather_facts kısmını kullanırız. Yukarıdaki örnekte dağıtım ismi ve versiyon bilgisini bu sayede çekilmektedir.

Sunucuda sudo yetkisi ihtiyacı olduğu durumda become komutunu yes/true yapmamız yeterlidir. Bu örnekte öyle bir ihtiyaç olmadığı için false konumda tutuyoruz.

Playbook’un gerçekleştireceği işlemler tasks kısmı altında yazılır. Buradaki işlemler yazılış sıralarına göre teker teker gerçekleştirilir.

sergen@ansible:~/test-area$ ansible-playbook distribution.yaml

PLAY [all] **********************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************
ok: [nfs]
ok: [kube22]
ok: [kube21]
ok: [kube20]

TASK [Dağıtım] ******************************************************************************************************
ok: [nfs] => {
    "msg": "Rocky"
}
ok: [kube20] => {
    "msg": "Ubuntu"
}
ok: [kube21] => {
    "msg": "Ubuntu"
}
ok: [kube22] => {
    "msg": "Ubuntu"
}

TASK [Dağıtım versiyonu] ********************************************************************************************
ok: [nfs] => {
    "msg": "8.4"
}
ok: [kube20] => {
    "msg": "20.04"
}
ok: [kube21] => {
    "msg": "20.04"
}
ok: [kube22] => {
    "msg": "20.04"
}

PLAY RECAP *********************************************************************************************************
kube20                     : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
kube21                     : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
kube22                     : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
nfs                        : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

Son olarak bir de içerisinde modülleri kullandığımız biraz kapsamlı bir örnek yapalım. Bu örnekle sunucularımızdaki tarih saat ayarlarını yapılandırmış olalım. Bunun için aşağıdaki playbook’u kullanacağız.

---
- name: Chrony Configuration
  hosts: all
  gather_facts: yes
  become: yes
  tasks:
    - name: Install chrony download with apt
      apt: 
        name: chrony
        state: present
      when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
    - name: Install chrony download with yum
      yum:
        name: chrony
        state: present
      when: ansible_distribution == 'Red Hat Enterprise Linux' or ansible_distribution == 'Rocky' or ansible_distribution == 'Centos'
    - name: Chrony configuration on debian based
      blockinfile:
        path: /etc/chrony/chrony.conf
        block: |
          server 0.tr.pool.ntp.org
          server 1.tr.pool.ntp.org
          server 2.tr.pool.ntp.org
          server 3.tr.pool.ntp.org
        state: present
      when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
    - name: Chrony configuration on rhel based
      blockinfile:
        path: /etc/chrony.conf
        block: |
          server 0.tr.pool.ntp.org
          server 1.tr.pool.ntp.org
          server 2.tr.pool.ntp.org
          server 3.tr.pool.ntp.org
        state: present
      when: ansible_distribution == 'Red Hat Enterprise Linux' or ansible_distribution == 'Rocky' or ansible_distribution == 'Centos'
    - name: Service
      service:
        name: chronyd
        state: restarted
        enabled: yes
    - name: Timezone configuration
      timezone:
        name: Europe/Istanbul

Örnek biraz karmaşık olduğu için playbook dosyamız da uzun oldu. Öncelikle playbook dosyasını yazmadan yapılması gereken işlem ile ilgili olarak bir plan çıkarttım. Elimizde iki farklı işletim sistemi olduğunu biliyorum ve bu işlemi tek bir playbook ile tamamlamak istiyorum. Bundan ötürü when komutunu sık sık kullandım. Bunun yanı sıra konfigürasyon dosyasına eklemem gereken satırlar olduğu için blockinfile modülünü kullandım. Tek bir satır ekleyecek olsam yahut bir satırda değişiklik yapacağım zaman fileinline modülü yeterli geliyor ancak burada çoklu satır ekleme durumu olduğu için blockinfile daha uygun oldu. Son olarak servisi yeniden başlattım ve timezone modülü ile tanım yaptım.

$ ansible-playbook chrony.yaml -K
BECOME password:

PLAY [Chrony Configuration] *****************************************************************************************

TASK [Gathering Facts] **********************************************************************************************
ok: [nfs]
ok: [kube22]
ok: [kube21]
ok: [kube20]

TASK [Install chrony download with apt] *****************************************************************************
skipping: [nfs]
changed: [kube22]
changed: [kube21]
changed: [kube20]

TASK [Install chrony download with yum] ****************************************************************************
skipping: [kube20]
skipping: [kube21]
skipping: [kube22]
changed: [nfs]

TASK [Chrony configuration on debian based] *************************************************************************
skipping: [nfs]
changed: [kube21]
changed: [kube20]
changed: [kube22]

TASK [Chrony configuration on rhel based] ***************************************************************************
skipping: [kube20]
skipping: [kube21]
skipping: [kube22]
changed: [nfs]

TASK [Service] ******************************************************************************************************
changed: [nfs]
changed: [kube22]
changed: [kube21]
changed: [kube20]

TASK [Timezone configuration] ***************************************************************************************
changed: [kube22]
changed: [kube21]
changed: [kube20]
changed: [nfs]

PLAY RECAP **********************************************************************************************************
kube20                     : ok=5    changed=4    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
kube21                     : ok=5    changed=4    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
kube22                     : ok=5    changed=4    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
nfs                        : ok=5    changed=4    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

İşlemden sonra tekrar tarih saat komutunu yürüttüğümüzde saatlerin eşitlendiği ve saat diliminin +3 olduğunu göreceğiz.

sergen@ansible:~/test-area$ ansible all -m command -a "date"
nfs | CHANGED | rc=0 >>
Wed May  4 21:58:00 +03 2022
kube22 | CHANGED | rc=0 >>
Wed 04 May 2022 09:58:01 PM +03
kube20 | CHANGED | rc=0 >>
Wed 04 May 2022 09:58:02 PM +03
kube21 | CHANGED | rc=0 >>
Wed 04 May 2022 09:58:02 PM +03

Ansible ile ilgili basit örnekler incelemek isterseniz Github hesabıma göz atabilirsiniz.