มาเรียนรู้การใช้งาน Ansible
Ansible คือ เครื่องมือที่ใช้สำหรับการจัดการ configuration โดยจะทำงานผ่านทาง ssh พัฒนาด้วย ภาษา python การทำงานจะงานในรูปแบบที่ต้องมี management node ที่ทำหน้าเป็น server หลังที่จะทำหน้าเป็น ผู้ที่จะต้องไปติดตั้ง package บน เครื่องserverในระบบ การทำงานจะประกอบด้วยfile ที่เกี่ยวข้อง 2 file คือ inventory file และ playbook
- inventory file ทำหน้าเก่็บรายชื่อของ server หรือ ip ของ server ที่จะใช้สำหรับเป้าหมายสำหรับการติดตั้ง โดยdefault หาไม่มีการกำหนด ใน option -i ก็จะใช้ inventory ตือ /etc/ansible/hosts เราสามารถที่จะแก้ไขค่านี้ได้โดยทำการแก้ไข file /etc/ansible/ansible.cfg
- playbook.yml ไฟล์นี้จะเป็นตัวระบุว่า จะให้ ansible ทำอะไรบ้างในแต่ละ server การเขียนไฟล์จะใช้เป็น format ของ yaml format โดยสามารถเขียนแบบที่เป็น static และ dynamic ก็ได้โดยถ้าหาเขียนแบบ dynamic โดยใช้ jinja2 template
ในการทดสอบการใช้งาน จะทดสอบการใช้งานผ่านทาง Vagrantfile โดยจะเป็นการสร้าง infrastructure ด้วย Vagrantfile ดังนี้ โดยสร้างบน provider virtualbox
Vagrantfile
# Defines our Vagrant environment # # -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| # create mgmt node config.vm.define :mgmt do |mgmt_config| mgmt_config.vm.box = "centos/7" mgmt_config.vm.hostname = "mgmt" mgmt_config.vm.network :private_network, ip: "10.0.15.10" mgmt_config.vm.provider "virtualbox" do |vb| vb.memory = "256" end mgmt_config.vm.provision :shell, path: "bootstrap-mgmt.sh" end # create load balancer config.vm.define :lb do |lb_config| lb_config.vm.box = "centos/7" lb_config.vm.hostname = "lb" lb_config.vm.network :private_network, ip: "10.0.15.11" lb_config.vm.network "forwarded_port", guest: 80, host: 8080 lb_config.vm.provider "virtualbox" do |vb| vb.memory = "256" end end # create some web servers # https://docs.vagrantup.com/v2/vagrantfile/tips.html (1..2).each do |i| config.vm.define "web#{i}" do |node| node.vm.box = "centos/7" node.vm.hostname = "web#{i}" node.vm.network :private_network, ip: "10.0.15.2#{i}" node.vm.network "forwarded_port", guest: 80, host: "808#{i}" node.vm.provider "virtualbox" do |vb| vb.memory = "256" end end end end
bootstrap-mgmt.sh
#!/bin/bash yum -y update yum -y install epel-release yum -y install ansible yum -y install git cat << EOF >> /etc/hosts 10.0.15.10 mgmt 10.0.15.11 lb 10.0.15.21 web1 10.0.15.22 web2 10.0.15.23 web3 10.0.15.24 web4 10.0.15.25 web5 10.0.15.26 web6 10.0.15.27 web7 10.0.15.28 web8 10.0.15.29 web9 EOF
inventory.ini
[lb] lb [web] web1 web2
โดยให้สร้าง bootstrap-mgmt.sh และ inventory.ini ไว้ระดับเดียวกับ Vagrantfile
$ tree . . ├── Vagrantfile └── bootstrap-mgmt.sh └── inventory.ini
ผลที่ได้ จะได้ infrastructure ดังนี้
$ vagrant up $ vagrant up Bringing machine 'mgmt' up with 'virtualbox' provider... Bringing machine 'lb' up with 'virtualbox' provider... Bringing machine 'web1' up with 'virtualbox' provider... Bringing machine 'web2' up with 'virtualbox' provider... ==> mgmt: Checking if box 'centos/7' is up to date... ==> mgmt: Clearing any previously set forwarded ports... ==> mgmt: Clearing any previously set network interfaces... ==> mgmt: Preparing network interfaces based on configuration... ... ==> web2: Setting hostname... ==> web2: Configuring and enabling network interfaces... ==> web2: Rsyncing folder: /Users/newton/Vagrant/c7_ansible/ => /vagrant ==> web2: Machine already provisioned. Run <code>vagrant provision</code> or use the <code>--provision</code> ==> web2: flag to force provisioning. Provisioners marked to run always will still run. $ vagrant status Current machine states: mgmt running (virtualbox) lb running (virtualbox) web1 running (virtualbox) web2 running (virtualbox) ## ตรวจสอบ ip $ vagrant ssh mgmt -c "ip a" $ vagrant ssh lb -c "ip a" $ vagrant ssh web1 -c "ip a" $ vagrant ssh web2 -c "ip a"
หมายเหตุ ถ้าไม่ได้ ip ให้ลองไปปรับ ค่าของ network adapter โดยไปยัง setting > Network ดังนี้
Adapter Type: Tserver(82543GC)
Promiscuous Mode: Allow All
การเชื่อมต่อไปยัง vagrant
## connect to vagrant on node mgmt $ vagrant ssh mgmt $ cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) $ hosts="lb web1 web2" $ for h in $hosts;do ping -c 4 $h; done $ ansible --version ansible 2.1.1.0 config file = /etc/ansible/ansible.cfg configured module search path = Default w/o overrides
การเชื่อมต่อ ระหว่างโหนดด้วย ssh
เนื่องจาก Ansible จะเชื่อมต่อไปยังเครื่อง remote ด้วย ssh โดยที่การเชื่อมต่อไม่มีprograme ที่ไปทำงานอยู่บน เครื่อง remote หรือที่มีจะเรียกว่า agent
เมื่อทำการเชื่อมต่อไปยัง เครื่องปลายทางครั้งแรกด้วย ssh เครื่องปลายทางจะทำการส่ง key มาให้กับเราเพื่อทำ ssh authentication เพื่อยอมรับการเชื่อมต่อ ยกตัวอย่างเช่นเมื่อ mgmt node ต้องการ ssh ไปยัง web1 ในครั้งแรก
$ ssh web1 The authenticity of host 'web1 (10.0.15.21)' can't be established. ECDSA key fingerprint is d1:2d:26:9d:9b:92:e9:0c:02:53:59:8b:b9:6a:c7:8f. Are you sure you want to continue connecting (yes/no)? no ## go to /vagrant $ cd /vagrant $ ls $ ansible web1 -i inititial.ini -m ping The authenticity of host 'web1 (10.0.15.21)' can't be established. ECDSA key fingerprint is d1:2d:26:9d:9b:92:e9:0c:02:53:59:8b:b9:6a:c7:8f. Are you sure you want to continue connecting (yes/no)? yes web1 | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true }
เมื่อทำการยืนยันด้วยการตอบ yes ค่า publickey ของ web1 จะมาเก็บไว้ในfile ~/.ssh/known_hosts
$ cat ~/.ssh/known_hosts web1,10.0.15.21 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLK8riXRk1/Gyzggg6u+R41F+CBR1ZB7j3dOgBPqOn2H6jGfi0CC5W934OM6OQzKboXTZkny2Q2hrRopCQvJGCg=
เราสามารถเรียกดู public key ได้ใช้คำสั่ง ssh-keyscan
$ ssh-keyscan web1 # web1 SSH-2.0-OpenSSH_6.6.1 web1 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDxFTURY5TNjR2YliYth1qf2JL++rhuDu8bAdX3cJ/uMmheUEk2Ay3a5n3Fwc64jOTIoFWfIRlKNjogdtAU0xNEb+ksLGDXoNZATM9i08AT/RsenZ1RMDt/R9AA+Q6cptw9TzM/MQLBzGKqannZnHqF7iYc6BXnmzly3lO9t10mc4CV7K0xm3sZT17cOuxUHIc+/Jh63JJJpd2MEXB933zjJFHLdlD/GDIlmOy+HZEjLs6VETwL1UDryvIXqQFa0al7gylS6phWiHa807rRV+luKQM662RP8X7EcAI0bSHxYkRopqL8EVXgWwYOkHOTk21EhDRDb52goLjdE5OmPDz9 # web1 SSH-2.0-OpenSSH_6.6.1 web1 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLK8riXRk1/Gyzggg6u+R41F+CBR1ZB7j3dOgBPqOn2H6jGfi0CC5W934OM6OQzKboXTZkny2Q2hrRopCQvJGCg=
อ่านค่า key มาเก็บไว้ใน ~/.ssh/known_hosts
$ ssh-keyscan lb web1 web2 >> ~/.ssh/known_hosts # web1 SSH-2.0-OpenSSH_6.6.1 # lb SSH-2.0-OpenSSH_6.6.1 # lb SSH-2.0-OpenSSH_6.6.1 # web1 SSH-2.0-OpenSSH_6.6.1 # web2 SSH-2.0-OpenSSH_6.6.1 # web2 SSH-2.0-OpenSSH_6.6.1
เมื่อใช้คำสั่ง ansible ก็จะไม่ถามให้ยอมรับ keyอีก
$ ansible lb -i inititial.ini -m ping lb | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true $ ansible web1 -i inititial.ini -m ping web1 | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true } $ ansible web2 -i inititial.ini -m ping web2 | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true }
Ansible Ad-hoc
การใช้คำสั่ง ansible ที่ผ่านมา เรียกว่าเป็นการสั่งแบบ ad-hoc เหมือนกับเราสั่งด้วยทีละคำสั่งเหมือนกับการสั่งใน commandline แต่เป็นการสั่งผ่าน ansible เท่านั้น ที่ผ่านมาจะเห็นว่า ansible บอกว่ายังไม่สามารถเชื่อมต่อผ่าน ssh ได้
-m เป็นการบอกว่า จะให้ module ใดทำงาน เช่น -m ping คือ ต้องการที่ใช้ module ping โดยที่ module ที่พูดถึงคือ python module นั้นเอง ความสามารถของ ansible สามารถขยายความสามารถได้ด้วย module และ module จะแบ่งออกเป็น 2 ส่วนได้แก่ core และ extra ซึ่งมีมากกว่า 250 module
## work on current user = vagrant $ ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/home/vagrant/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/vagrant/.ssh/id_rsa. Your public key has been saved in /home/vagrant/.ssh/id_rsa.pub. The key fingerprint is: bd:e8:d3:1b:06:53:7b:b2:16:2d:5f:55:30:58:56:65 vagrant@mgmt The key's randomart image is: +--[ RSA 2048]----+ | o=oE| | .. o.| | . .| | o o . | | S * o . | | + O . | | ..* . | | ..o.. | | .... | +-----------------+
## run ad-hoc ansible $ cd /vagrant $ ansible all -i inititial.ini -m ping --ask-pass SSH password: lb | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true } web2 | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true } web1 | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true }
หากเกิด error
Fix error ssh fails with error as permission denied
## try ssh to localhost on mgmt $ ssh localhost Permission denied (publickey,gssapi-keyex,gssapi-with-mic) $ ssh lb Permission denied (publickey,gssapi-keyex,gssapi-with-mic). $ ssh web1 Permission denied (publickey,gssapi-keyex,gssapi-with-mic). $ ssh web2 Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
สำหรับการติดตั้ง Centos 7 ด้วย vagrant default ไม่อนุญาติให้ใช้ password สำหรับการ login ด้วย password
## check /etc/ssh/sshd_config , PasswordAuthentication no
ให้เปลี่ยนค่า config ใน ทุก node mgmt, lb, web1, web2
$ sudo su - # vi /etc/ssh/sshd_config PasswordAuthentication yes # systemctl restart sshd
ทดสอบ vagrant ad-hoc อีกครั้ง
$ ansible all -i inititial.ini -m ping --ask-pass SSH password: web2 | SUCCESS => { "changed": false, "ping": "pong" } lb | SUCCESS => { "changed": false, "ping": "pong" } web1 | SUCCESS => { "changed": false, "ping": "pong" }
หมายความว่า mgmt node สามารถติดต่อไปยัง เครื่อง อื่นได้ผ่านทาง ssh คราวนี้ให้ลองสั่งอีกครั้งโดยไม่มี –ask-pass จะเห็นว่าก็ยังคงทำงานได้อยู่ แต่คราวนี้ จะเร็วกว่าเดิม โดย ansible จะทำการ cache ไว้ใช้ชั่วคราวเท่านั้น
$ ps -x 11939 ? Ss 0:00 ssh: /home/vagrant/.ansible/cp/ansible-ssh-web1-22-vagrant [mux] 11942 ? Ss 0:00 ssh: /home/vagrant/.ansible/cp/ansible-ssh-lb-22-vagrant [mux] 11945 ? Ss 0:00 ssh: /home/vagrant/.ansible/cp/ansible-ssh-web2-22-vagrant [mux] ## try again win no password $ ansible all -i inititial.ini -m ping web1 | SUCCESS => { "changed": false, "ping": "pong" } web2 | SUCCESS => { "changed": false, "ping": "pong" } lb | SUCCESS => { "changed": false, "ping": "pong" }
ตัวอย่างของ การสั่ง adhoc ด้วย module shell
อ่านเพิ่มเติม http://docs.ansible.com/ansible/intro_adhoc.html
$ ansible all -m shell -a "uname -a" -i inititial.ini web1 | SUCCESS | rc=0 >> Linux web1 3.10.0-327.22.2.el7.x86_64 #1 SMP Thu Jun 23 17:05:11 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux web2 | SUCCESS | rc=0 >> Linux web2 3.10.0-327.22.2.el7.x86_64 #1 SMP Thu Jun 23 17:05:11 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux lb | SUCCESS | rc=0 >> Linux lb 3.10.0-327.22.2.el7.x86_64 #1 SMP Thu Jun 23 17:05:11 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
โดยคำสั่ง ที่ต้องการสั่งจะอยู่ใน option -a
Ansible playbook link
นอกจากการสั่ง ให้ ansible ทำงานแบบ ad-hoc แล้ว ยังสามารถให้ทำงานผ่านทาง playbook โดยมีการเขียนในแบบ ภาษา yaml โดย ansible จะใช้ playbook เป็น configuration management ให้แก่ระบบ การประกาศค่าต่างๆ ใน playbook นั้นไม่ยากอ่านง่าย ดังตัวอย่าง
ssh-addkey.yml จะทำหน้าที่ copy key จาก mgmt ไปยังเครื่องอื่นๆ
--- - hosts: all sudo: yes gather_facts: no remote_user: vagrant tasks: - name: install ssh key authorized_key: user=vagrant key="{{ lookup('file', '/home/vagrant/.ssh/id_rsa.pub') }}" state=present
การใช้งาน playbook จะใช้คำสั่ง ansible-playbook ดังนี้
cd /vagrant $ ansible-playbook ssh-addkey.yml -i inititial.ini --ask-pass SSH password: [DEPRECATION WARNING]: Instead of sudo/sudo_user, use become/become_user and make sure become_method is 'sudo' (default). This feature will be removed in a future release. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. PLAY [all] ********************************************************************* TASK [install ssh key] ********************************************************* changed: [web2] changed: [lb] changed: [web1] PLAY RECAP ********************************************************************* lb : ok=1 changed=1 unreachable=0 failed=0 web1 : ok=1 changed=1 unreachable=0 failed=0 web2 : ok=1 changed=1 unreachable=0 failed=0
ถึงขั้นตอนนี้ เราได้สร้าง ssh trust ระหว่าง node เรียบร้อย เราสามารถที่สั่ง run playbook ซ้ำๆ ได้ ansible มีความฉลาดพอ เพื่อดูว่ามีการเปลี่ยนแปลงของ config หรือไม่ หาไม่มีการเปลี่ยนแปลง ansible ก็จะไม่ดำเนินการใด เรียกได้ว่า ansible มีคุณสมบัติของ “idempotence”
$ ansible-playbook ssh-addkey.yml -i inititial.ini --ask-pass SSH password: [DEPRECATION WARNING]: Instead of sudo/sudo_user, use become/become_user and make sure become_method is 'sudo' (default). This feature will be removed in a future release. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. PLAY [all] ********************************************************************* TASK [install ssh key] ********************************************************* ok: [web1] ok: [web2] ok: [lb] PLAY RECAP ********************************************************************* lb : ok=1 changed=0 unreachable=0 failed=0 web1 : ok=1 changed=0 unreachable=0 failed=0 web2 : ok=1 changed=0 unreachable=0 failed=0
จะเห็นว่า change=0
--- - hosts: all sudo: yes gather_facts: no tasks: - name: install chrony yum: name=chrony state=installed update_cache=yes - name: write our chrony.conf copy: src=/vagrant/files/chrony.conf dest=/etc/chrony.conf mode=644 owner=root group=root notify: restart chrony - name: start chrony service: name=chronyd state=started handlers: - name: restart chrony service: name=chronyd state=restarted
$ ansible-playbook -i inititial.ini chrony-install.yml [DEPRECATION WARNING]: Instead of sudo/sudo_user, use become/become_user and make sure become_method is 'sudo' (default). This feature will be removed in a future release. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. PLAY [all] ********************************************************************* TASK [install chrony] ********************************************************** ok: [web2] ok: [web1] ok: [lb] TASK [write our chrony.conf] *************************************************** ok: [web1] ok: [lb] ok: [web2] TASK [start chrony] ************************************************************ ok: [web1] ok: [web2] ok: [lb] PLAY RECAP ********************************************************************* lb : ok=3 changed=0 unreachable=0 failed=0 web1 : ok=3 changed=0 unreachable=0 failed=0 web2 : ok=3 changed=0 unreachable=0 failed=0
chrony-remove.yml
--- - hosts: all sudo: yes gather_facts: no tasks: - name: remove chrony yum: name=chrony state=absent
run playbook: chrony-remove.yml
$ ansible-playbook -i inititial.ini chrony-remove.yml [DEPRECATION WARNING]: Instead of sudo/sudo_user, use become/become_user and make sure become_method is 'sudo' (default). This feature will be removed in a future release. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. PLAY [all] ********************************************************************* TASK [remove chrony] *********************************************************** changed: [web1] changed: [lb] changed: [web2] PLAY RECAP ********************************************************************* lb : ok=1 changed=1 unreachable=0 failed=0 web1 : ok=1 changed=1 unreachable=0 failed=0 web2 : ok=1 changed=1 unreachable=0 failed=0