Ansible Fundamentals - Ad-hoc Commands



  • Introduction.

    Việc triển khai và quản lý servers một cách đáng tin cậy và hiểu quả không phải là một công việc đơn giản. Khi những công cụ như Ansible chưa xuất hiện, luôn có một rào cản giữa các quản trị viên hệ thống (system administrators) với các nhà phát triển (developers) cũng như người dùng cuối (users). Các quản trị viện thường phải quản lý servers một cách thủ công từ những công việc như cài đặt phần mềm, thay đổi cấu hình và quản lý các services. Đối với các hệ thống nhỏ, việc quản lý như vậy có thể chấp nhận được. Tuy nhiên với các hệ thống phức tạp với hàng trăm servers, việc quản lý như vậy là không hiểu quả (nêu không muốn nói là không thể) và thường gây ra lỗi.

    Chính vì lý do đó, việc có các công cụ để quản lý cấu hình - configuration management cũng như hỗ trợ quá trình server provisioning là cần thiết. Thay vì phải triển khai (deploying), thay đổi/cập nhật (patching) và hủy bỏ (destroying) servers thủ công, các công việc đó có thể được tự động hóa với rất ít hoặc không có sự can thiệp từ các quản trị viên.

    Khi hệ thống ứng dụng trở nên phức tạp hơn, việc tách biệt quản trị viên hệ thống và các nhà phát triển là không được khuyến khích. Chúng ta cần tích hợp công việc của hai bên lại nếu có thể. Đó cũng là một trong những nguyên nhân mà chúng ta có thuật ngữ DevOps hay SRE - Site Reliability Engineering. Chúng ta sẽ không bàn đến định nghĩ của các khái niêm kia do nó đôi khi quá trừu tượng. Khi các nhà phát triển coi infrastructure như một phần quan trọng trong quá trình phát triển ứng dụng, độ ổn định cũng như hiệu năng sẽ có tính quy chuẩn cao hơn. Ngược lại khi các quản trị viên hệ thống (hay sysadmins) làm việc chặt chẽ hơn với các nhà phát triển, tốc độ phát triển ứng dụng sẽ được cải thiện. Từ đó sẽ có nhiều thời gian hơn cho việc cải thiện hiệu năng hệ thống cũng như đưa ra các thử nghiệm mới.

    Ansible là một công cụ giúp cho việc thực hiện DevOps dễ dàng hơn. Nó có thể được coi như một ngôn ngữ chung giữa sysadmins, developers hay thậm chí là cả người dùng.

    Why using Ansible?

    Các bạn có thể tìm hiểu thêm ở đây - https://www.ansible.com/overview/it-automation cũng như trong Documentation của Ansible. Tuy nhiên có một số điểm sau đây khiến cho Ansible trở thành một công cụ khá phổ biến:

    • Clear & Simple: Ansible sử dụng YAML một định dang dữ liệu tương tự JSON nhưng đơn giản hơn khá nhiều. Chúng ta chỉ cần làm quen mới một số thành phần trong YAML như variables, objects, lists, data types, anchors, references là có thể đọc và hiểu Ansible. YAML khá dễ dàng đối với các developers và sysadmins.
    • Pretty Fast: Ansible rất dễ học và sử dụng ở mức cơ bản. Ansible là agentless do chúng ta không cần cài đặt các agents hay daemons trên các servers.
    • Complete && Efficient: Thông thường chúng ta phải sử dụng các công cụ khác nhau cho việc quản lý cấu hình - configuration management (Puppet, Chef), deployment (Capistrano, Fabric) hay thực hiện các lệnh ad-hoc (SSH). Ansible có thể kết hợp ba việc đó lại và giấu đi hầu hết các công việc phức tạp cần thực hiện.
    • Secure: Ansible sử dụng SSH để quản lý các hosts.

    Learning Resources.

    Tất nhiên bài viết của mình không thể đề cập mọi khía cạnh của Ansible một cách chi tiết (mình cũng chỉ làm quen với Ansible trong một hai tháng trở lại đây). Sau khi đã làm quen với các kiến thức cơ bản, bạn có thể tìm hiểu và đọc thêm tài liệu ở một số nguồn sau đây:

    Development environment.

    Ansible có thể làm việc với remote hoặc local server miễn là chúng ta có quyền truy cập đến các server đó. Tuy nhiên để thuận tiện cho việc testing chúng ta sẽ sử dụng local servers. Thông thường chúng ta sẽ thực hiện các công việc trên local server trước khi triển khai lên production server (TDD). Để thực hiện các ví dụ trong các phần tiếp theo, chúng ta sẽ sử dụng Vagrant để cài đặt các local servers (virtual machines). Chúng ta sẽ cần cài Virtual BoxVagrant (việc này khá đơn giản nên mình sẽ không đề cập ở đây).

    Lưu ý servers trong ví dụ của chúng ta sẽ là Debian (cụ thể là Ubuntu 16.04). Mình thường sử dụng bento/ubuntu-16.04, tuy nhiên bạn có thể sử dụng các vagrant box khác nếu muốn. Nếu bạn dùng CentOS hay loại server nào khác, một số command, service có thể sẽ có sự khác biệt.

    Một trong những tính năng quan trọng của Vagrant đó là Server provisioning. Khi chạy lệnh vagrant up lần đầu tiên, Vagrant sẽ tự động provisions các virtual machines được khởi tạo sử dụng các provisioners được định nghĩa trong Vagrantfile. Sau khi các khởi tạo các virtual machines, chúng ta có thể sử dụng lệnh vagrant provision để chạy lại các provisioners nếu cần thiết.

    Ansible là một trong những provisioner được cung cấp sẵn bởi Vagrant (cùng với một số provisioner khác như shell, Chef, Docker, hay Puppet). Khi chúng ta sử dụng vagrant provision hay vagrant up (lần gọi đầu tiên), Vagrant sẽ trao quyền xử lý cho Ansible, ở đây Ansible có thể chạy một playbook được định nghĩa trước. Dưới đây là một cách sử dụng Ansible provisioner với Vagrant đơn giản:

    # Inside Vagrantfile
    config.vm.provision "ansible" do |ansible|
        ansible.playbook = "main.yml"
    end
    

    Ở đây main.yml sẽ là một Ansible playbook (sẽ được bàn luận trong các bài viết sau) cùng thư mục với Vagrantfile.

    Sau đây chúng ta sẽ định nghĩa 3 local servers đơn giản để thuận tiện cho việc trình bày trong phần sau. Ba local servers đó bao gồm 2 application servers và 1 database server được minh họa trong hình bên dưới:

    Thông thường chúng ta sẽ có một load balancer đứng trước hai application servers. Để đơn giản cho việc minh họa các ví dụ chúng ta sẽ loại bỏ load balancer ở đây đi.

    Nội dụng của Vagrantfile sẽ như sau:

    # -*- mode: ruby -*-
    # vi: set ft=ruby :
    
    Vagrant.configure("2") do |config|
      config.vm.box = "bento/ubuntu-16.04"
      config.ssh.insert_key = false
      config.vm.synced_folder ".", "/vagrant", disabled: true
      config.vm.provider :virtualbox do |v|
        v.memory = 256
        v.linked_clone = true
      end
    
      config.vm.define "app1" do |app|
        app.vm.hostname = "app1.test"
        app.vm.network :private_network, ip: "192.168.60.4"
      end
    
      config.vm.define "app2" do |app|
        app.vm.hostname = "app2.test"
        app.vm.network :private_network, ip: "192.168.60.5"
      end
    
      config.vm.define "db" do |db|
        db.vm.hostname = "db.test"
        db.vm.network :private_network, ip: "192.168.60.6"
      end
    end
    

    Các thông số có thể thay đổi theo ý muốn, mỗi server sẽ được gán với một private IPv4 address. Chạy lệnh vagrant up chúng ta sẽ có môi trường để thử nghiệm Ansible.

    :bird:

    Ad-hoc commands.

    Một trong những điểm mạnh của Ansible là nó cho phép chúng ta thực hiện các shell commands ở dạng nguyên bản (raw, verbatim) hoặc thông qua một số module cung cấp sẵn như command hay shell. Hơn nữa chúng ta có thể chuyển đổi các shell scripts có sẵn thành Ansible playbook nếu cần thiết. Các command trong Ansible sẽ được thực hiện trên một hoặc một nhóm các hosts. Thay vì phải logging in sử dụng SSH và thay đổi cấu hình, chúng ta chỉ cần chạy một command và mọi thay đổi sẽ được đẩy đến các target hosts. Đối với các quy trình phức tạp, Ansible playbook sẽ là lựa chọn ưu tiên hơn so với ad-hoc command. Tuy nhiên chúng ta có thể sử dụng ad-hoc commands để thu thập một số thông tin về các servers đang được quản lý. Cụ thể một số công việc đó sẽ là:

    • Kiểm tra tài nguyên hệ thống (CPU usage, RAM, disk space, swap, hay networking).
    • Kiểm tra logs.
    • Quản lý users và groups.
    • Rebooting servers.
    • Quản lý cron jobs.
    • ...

    Ansible cho phép thực hiện command trên một hoặc hàng trăm servers cùng một lúc (có thể thực hiện song song thay vì tuần tự) sử dụng ansible command.

    Terminologies

    Trước tiên chúng ta sẽ tìm hiểu một số thuật ngữ thường được sử dụng mà đôi khi gây khó hiểu cho người đọc:

    • Idempotence: một cách tổng quát thì từ này chỉ một công đoạn (operation) mà khi thực hiện một hay nhiều lần đều cho ra cùng một kết quả. Đối với một công cụ quản lý cấu hình như Ansible, điều quan trong là cấu hình của các hosts sẽ được duy trì cho dù chúng ta chạy công cụ đó một hay nhiều lần. Một ví dụ là khi triển khai servers sử dụng shell scripts, các script đó có thể cho ra các kết quả không mong muốn nếu thực hiện nhiều lần lặp lại. Ansible bảo vệ chúng ta khỏi vấn đề đó, Ansible sẽ xác định các thay đổi cần thiết về cấu hình trên các hosts tự động. Do đó chúng ta có thể chạy một playbook nhiều lần mà không làm thay đổi cấu hình hiện tại sau lần deploy đầu tiên. Trong các bài viết sau chúng ta sẽ đề cập đến Ansible module cách mà Ansible làm cho các thao tác trở nên indempotent.
    • Ad-hoc: từ này có nghĩa kiểu như không được dự tính hay lên kế hoạch trước khi thực hiện một mục tiêu nào đó. Trong khuôn khổ của Ansible, khái niệm này có thể được hiểu là việc sử dụng /usr/bin/ansible để thực hiện một số command đơn giản. Trong đó chúng ta không sử dụng các cấu trúc được định nghĩa như trong playbook (sử dụng /usr/bin/ansible-playbook để chạy). Một ví dụ là kiểm tra dung lượng bộ nhớ còn lại trong một nhóm các servers, chúng ta có thể sử dụng ad-hoc command. Mọi ad-hoc commands đều có thể được chuyển về dạng playbook. Tuy nhiên sử dụng ad-hoc commands là một cách nhanh nhất để làm quên với Ansible.

    Simple inventory.

    Inventory trong Ansible sẽ được đề cập đến trong một bài viết khác. Hiện giờ có thể hiểu đơn giản đó là nơi các hosts được định nghĩa (có thể là domain name hay IP address). Các hosts có thể được định nghĩ riêng biệt hoặc nhóm lại, từ đó cho phép Ansible lựa chọn được danh sách các hosts cần thiết. Inventory là một file được viết dưới dạng YAML hoặc INI, tuy nhiên INI thường được lựa chọn do nó khá đơn giản và clean.

    Chúng ta sẽ định nghĩa file inventory như sau (tên file là tùy ý):

    [app]
    192.168.60.4
    192.168.60.5
    
    [db]
    192.168.60.6
    
    [multi:children]
    app
    db
    
    [multi:vars]
    ansible_user = vagrant
    ansible_ssh_private_key_file = ~/.vagrant.d/insecure_private_key
    

    Chúng ta không cần quan tâm quá chi tiết đến cấu trúc của file inventory trên. Một số điểm cần lưu ý:

    • [app][db] là hai nhóm (group) được định nghĩa, mỗi nhóm sẽ chứa một hoặc nhiều hosts (ở đây là IP address).
    • [multi:children] là một nhóm chứa hai nhóm chúng ta vừa tạo. Chúng ta có thể sử dụng nhóm mặc định là [all] mà Ansible cung cấp sẵn ở đây. Tuy nhiên để cụ thế, chúng ta sẽ tạo một nhóm mới.
    • [multi:vars] là danh sách các variables áp dụng cho nhóm multi (trong trường hợp của chúng ta là cho tất cả các servers).

    Mặc định file inventory được Ansible sử dụng sẽ là /etc/ansible/hosts và file cấu hình mặc định của Ansible sẽ là /etc/ansible/ansible.cfg. Nếu trong môi trường production, các file đó được khuyến khích sử dụng. Tuy nhiên khi tìm hiểu hoặc thử nghiệm trên Ansible, chúng ta nên tạo một file inventory riêng biệt. Sử dụng ansible -i <path_to_inventory_file> để trỏ đến một file inventory khác file mặc định.

    ansible_useransible_ssh_private_key_file là hai biến được Ansible sử dụng để khởi tạo SSH connection đến các servers. Nếu chúng ta không định nghĩa hai biến đó, khi sử dụng ansible command chúng ta sẽ cần cung cấp thêm hai options tương ứng là --become-user--private-key.

    Run shell commands.

    Không nói nhiều nữa, chúng ta sẽ cùng bắt đầu với một số ad-hoc command đơn giản nhất.

    Giả sử chúng ta muốn kiểm tra hostname của các servers xem đã được cấu hình đúng hay chưa. Thông thường chúng ta sẽ sử dụng command hostname để thực hiện công việc đó. Hãy xem Ansible sẽ làm như thế nào:

    ansible -i inventory.ini multi -a "hostname"
    

    Ansible command trên có thể dịch như sau: "Với tất cả các hosts trong nhóm multi chạy command hostname và trả về kết quả". Command trên sẽ trả về kết quả tương tự như sau:

    192.168.60.6 | SUCCESS | rc=0 >>
    db
    
    192.168.60.4 | SUCCESS | rc=0 >>
    app1
    
    192.168.60.5 | SUCCESS | rc=0 >>
    app2
    

    OK, hostname của các servers đã chính xác và không có server nào trả về lỗi. :cyclone: :gorilla:

    Để ý rằng thứ tự kết quả trả về từ các server không giống với thứ tự mà các server được định nghĩa trong inventory file. Thực hiện command trên một vài lần, thứ tự trả về cũng sẽ thay đổi.

    Nguyên nhân là mặc định các commands sẽ được thực hiện song song (in parallel) trên các servers nhằm tăng hiệu suất. Ansible sẽ sử dụng process forks để thực hiện công việc đó. Nếu chúng ta chỉ quản lý một hoặc một vài servers, việc thực hiện các commands một cách tuần tự (serially) sẽ không có khác biệt nhiều so với thực hiện song song. Tuy nhiên khi số lượng servers là lớn, thời gian thực thi sẽ được cải thiện đáng kể.

    Chúng ta có thể điều chỉnh số lượng process forks khi thực hiện Ansible command thông qua -f option. Đặt số lượng process forks1 nếu muốn thực hiện các command tuần tự:

    ansible -i inventory.ini multi -a "hostname" -f 1
    

    Kết quả trả về sẽ như sau:

    192.168.60.4 | SUCCESS | rc=0 >>
    app1
    
    192.168.60.5 | SUCCESS | rc=0 >>
    app2
    
    192.168.60.6 | SUCCESS | rc=0 >>
    db
    

    Số lượng process forks phù hợp sẽ phụ thuộc vào cấu hình và network connection của hệ thống.

    Ansible command trên có thể được viết lại như sau:

    ansible -i inventory.ini -a "hostname" multi
    

    Ở đây vị trị của group được đặt sau command cần thực hiên. Command này không thay đổi về chức năng, nó chỉ khác nhau ở cách chúng ta translating command đó mà thôi.

    "Trên A servers thực hiện command B" hay "Thực hiện command B trên A servers"

    Hãy cùng thực hiện một số ad-hoc command khác xem sao:

    • Kiểm tra dung lượng đĩa còn trống: ansible -i inventory.ini multi -a "df -h" -f 10
    192.168.60.4 | SUCCESS | rc=0 >>
    Filesystem                    Size  Used Avail Use% Mounted on
    udev                           99M     0   99M   0% /dev
    tmpfs                          24M  3.9M   20M  17% /ru
    /dev/mapper/vagrant--vg-root   62G  1.3G   58G   3% /
    tmpfs                         119M     0  119M   0% /dev/shm
    tmpfs                         5.0M     0  5.0M   0% /run/lock
    tmpfs                         119M     0  119M   0% /sys/fs/cgroup
    /dev/sda1                     472M   58M  391M  13% /boot
    tmpfs                          24M     0   24M   0% /run/user/1000
    
    192.168.60.5 | SUCCESS | rc=0 >>
    Filesystem                    Size  Used Avail Use% Mounted on
    udev                           99M     0   99M   0% /dev
    tmpfs                          24M  3.9M   20M  17% /run
    /dev/mapper/vagrant--vg-root   62G  1.3G   58G   3% /
    tmpfs                         119M     0  119M   0% /dev/shm
    tmpfs                         5.0M     0  5.0M   0% /run/lock
    tmpfs                         119M     0  119M   0% /sys/fs/cgroup
    /dev/sda1                     472M   58M  391M  13% /boot
    tmpfs                          24M     0   24M   0% /run/user/1000
    
    192.168.60.6 | SUCCESS | rc=0 >>
    Filesystem                    Size  Used Avail Use% Mounted on
    udev                           99M     0   99M   0% /dev
    tmpfs                          24M  3.9M   20M  17% /run
    /dev/mapper/vagrant--vg-root   62G  1.3G   58G   3% /
    tmpfs                         119M     0  119M   0% /dev/shm
    tmpfs                         5.0M     0  5.0M   0% /run/lock
    tmpfs                         119M     0  119M   0% /sys/fs/cgroup
    /dev/sda1                     472M   58M  391M  13% /boot
    tmpfs                          24M     0   24M   0% /run/user/1000
    
    • Kiểm tra RAM và Swap Area: ansible -i inventory.ini multi -a "free -m" -f 10
    192.168.60.5 | SUCCESS | rc=0 >>
                  total        used        free      shared  buff/cache   available
    Mem:            236          49          64           3         122         160
    Swap:          1023           0        1023
    
    192.168.60.4 | SUCCESS | rc=0 >>
                  total        used        free      shared  buff/cache   available
    Mem:            236          49          62           3         124         160
    Swap:          1023           0        1023
    
    192.168.60.6 | SUCCESS | rc=0 >>
                  total        used        free      shared  buff/cache   available
    Mem:            236          49          62           3         123         160
    Swap:          1023           0        1023
    
    • Quản lý các service, ví dụ như Time Synchronization - NTP chẳng hạn. Đầu tiên chúng ta cần cài đặt service đó sử dụng ansible -i inventory.ini app -b -m apt -a "name=ntp state=present" command. Nếu chúng ta muốn ntp daemon tự động khởi động khi booting hệ thống, sử dụng Ansible command đơn giản sau:
    ansible -i inventory.ini app -b -m service -a "name=ntp state=started enabled=yes"
    

    Kết quả trả về sẽ tương tự như sau:

    192.168.60.5 | SUCCESS => {
        "changed": false, 
        "enabled": true, 
        "name": "ntp", 
        "state": "started", 
        "status": {
            "ActiveEnterTimestamp": "Tue 2018-02-27 08:10:33 UTC", 
            "ActiveEnterTimestampMonotonic": "19213778887", 
            ...
        }
    }
    

    Configuring our servers.

    Hiện tại các servers của chúng ta (gồm 2 application server và 1 database server) chưa được cài đặt các phần mềm cần thiết. Để đơn giản chúng ta giả sử rằng, các application server sử dụng Django framework, và MySQL cho database server. Hãy cùng cài đặt các phần mềm cần thiết trên các server.

    Application servers.

    Công việc đầu tiên là kiểm tra Python version hiện tại. Đối với Ubuntu 16.04, Python đã được cài đặt sẵn với hai phiên bản là 2.73.5:

    ansible -i inventory.ini app -a "python --version"
    

    Kết quả thu được sẽ như sau:

    192.168.60.5 | SUCCESS | rc=0 >>
    Python 2.7.12
    
    192.168.60.4 | SUCCESS | rc=0 >>
    Python 2.7.12
    

    Tuy nhiên nếu chúng ta muốn cài đặt Django phiên bản 2.x, phiên bản Python cần sử dụng phải >=3.4

    Để thuận tiện chúng ta sẽ tiến hành thay đổi phiên bản Python mặc định trên các application servers từ 2.7 thành 3.5. Để ý rằng /usr/bin/python là một symbolic link đến /usr/bin/python2.7. Công việc đầu tiên là loại bỏ symbolic link này sử dụng Ansible file module như sau:

    ansible -i inventory.ini app -b -m file -a 'dest=/usr/bin/python state=absent'
    

    Kết quả trả về sẽ như sau:

    192.168.60.4 | SUCCESS => {
        "changed": true, 
        "path": "/usr/bin/python", 
        "state": "absent"
    }
    192.168.60.5 | SUCCESS => {
        "changed": true, 
        "path": "/usr/bin/python", 
        "state": "absent"
    }
    

    Tuy nhiên một vấn đề ở đây là Ansible modules được viết bằng Python và mặc định Ansible sẽ sử dụng Python binary tại /usr/bin/python. Do chúng ta đã xóa symbolic link ở trên, sẽ có lỗi khi thực hiện các Ansible command tiếp theo:

    192.168.60.4 | FAILED! => {
        "changed": false, 
        "module_stderr": "Shared connection to 192.168.60.4 closed.\r\n", 
        "module_stdout": "/bin/sh: 1: /usr/bin/python: not found\r\n", 
        "msg": "MODULE FAILURE", 
        "rc": 0
    }
    192.168.60.5 | FAILED! => {
        "changed": false, 
        "module_stderr": "Shared connection to 192.168.60.5 closed.\r\n", 
        "module_stdout": "/bin/sh: 1: /usr/bin/python: not found\r\n", 
        "msg": "MODULE FAILURE", 
        "rc": 0
    }
    

    Để khắc phục vấn đề này chúng ta có thể tạm thời thay đổi đường dẫn đến Python binary mà Ansible sử dụng. Thêm dòng sau vào cuối file inventory:

    ansible_python_interpreter = /usr/bin/python3
    

    Tiếp theo chúng ta sẽ tạo mới một symbolic link trỏ đến phiên bản 3.5 của Python sử dụng file module như sau:

    ansible -i inventory.ini app \
        -b \
        -m file \
        -a 'src=/usr/bin/python3.5 dest=/usr/bin/python state=link'
    

    Ok, giờ chúng ta sẽ khôi phục lại file inventory là kiểm tra phiên bản python hiện tại:

    ansible -i inventory.ini app -a "python --version"
    

    Kết quả trả về:

    192.168.60.5 | SUCCESS | rc=0 >>
    Python 3.5.2
    
    192.168.60.4 | SUCCESS | rc=0 >>
    Python 3.5.2
    

    Quay trở lại với nhiệm vụ của chúng ta, các package cần thiết phải cài đặt là python3-dev, python3-setuptoolspython-mysqldb (Python interface cho MySQL). Chúng ta có thể kiểm các package trên trong apt cache sử dụng một số command sau:

    sudo apt -n search python-mysqldb
    sudo apt show python-mysqldb
    

    Việc cài đặt các package trên hai application server là khá đơn giản khi sử dụng Ansible:

    ansible -i inventory.ini app -b -m apt -a "name=python3-dev state=present"
    ansible -i inventory.ini app -b -m apt -a "name=python3-setuptools state=present"
    ansible -i inventory.ini app -b -m apt -a "name=python-mysqldb state=present"
    

    Công việc cuối cùng là cài đặt Django framework. Ở đây chúng ta sẽ dùng pip module thay vì apt module để cài đặt phiên bản mới nhất của framework. Sử dụng command sau:

    ansible -i inventory.ini app -b -m pip \
        -a "name=Django state=present executable=/usr/bin/pip3"
    

    Chú ý ở đây ta sử dụng executable option để chỉ định phiên bản Python PIP mà chúng ta sẽ dùng là /usr/bin/pip3.

    Để kiểm tra phiên bản hiện tại và đảm bảo Django framework đã được cài đặt, sử dụng command sau:

    ansible -i inventory.ini app -a "python3 -c 'import django; print(django.get_version())'" 
    

    Kết quả trả về sẽ tương tự như sau:

    192.168.60.5 | SUCCESS | rc=0 >>
    2.0.2
    
    192.168.60.4 | SUCCESS | rc=0 >>
    2.0.2
    

    Chúng ta sẽ tạm dừng ở đây, trên thực tế còn rất nhiều công việc phải làm khi cài đặt và triển khai Django project như cài đặt gunicorn, nginx, hay sử dụng Python Virtualenv thay vì sử dụng trực tiếp Python của hệ thống.

    :cyclone: :cyclone: :cyclone:

    Database server.

    Chúng ta sẽ sử dụng MariaDB trong ví dụ này. Với Ubuntu chúng ta có thể dùng mariadb-servermariadb-client package và cài đặt sử dụng apt module như sau:

    ansible -i inventory.ini db -b -m apt -a "name=mariadb-server state=present"
    ansible -i inventory.ini db -b -m apt -a "name=mariadb-client state=present"
    

    Tiếp theo chúng ta cần đảm bảo MariaDB service - mysql được khởi tạo và tự động load khi booting system sử dụng service module của Ansible:

    ansible -i inventory.ini db -b -m service -a "name=mysql state=started enabled=yes"
    

    Chúng ta có thể điểu chỉnh firewall của database server sử dụng iptables hoặc ufw để cho phép traffic qua cổng 3306 mà MariaDB sử dụng.

    Nếu chúng ta thử kết nối đến MariaDB từ các application servers sẽ có lỗi xảy ra do chúng ta cần phải cấu hình cho MariaDB. Thông thường chúng ta sẽ SSH vào server và sử dụng mysql_secure_installation command. Tuy nhiên Ansible đã cung cấp sẵn cho chúng ta một số module để làm việc với MariaDB (hay MySQL).

    Package python-mysqldb ở trên cần phải được cài đặt nếu muốn sử dụng các module liên quan đến MySQL.

    Tiếp theo chúng ta sẽ tạo một user và một database mới sử dụng Ansible.

    User có thể được tạo mới sử dụng mysql_user module như sau:

    ansible -i inventory.ini db -b \
        -m mysql_user \
        -a "name=admin host=% password=12345678 priv=*.*:ALL state=present"
    

    Để ý rằng password ở đây được định nghĩa ở dạng clear text. Sau này khi sử dụng playbook, việc lưu pasword như vậy sẽ không an toàn. Thay vào đó chúng ta sẽ sử dụng Ansible Prompts để yêu cầu người dùng nhập password hoặc Ansible Vault để lưu mật khẩu hoặc các thông tin private quan trọng.

    Việc tạo mới một database cũng khá đơn giản bằng cách sử dụng mysql_db module:

    ansible -i inventory.ini db \
        -b \
        -m mysql_db \
        -a "name=test_db login_user=admin login_password=12345678 state=present"
    

    Tương tự chúng ta sẻ sử dụng Prompts cho login_password thay vì lưu ở dạng clear text. Chúng ta sẽ giữ password ở dạng đó để tiện cho việc minh họa.

    OK, bây giờ chúng ta có thể connect đến test_db từ application servers sử dụng username là admin và password là 12345678

    Trên thực tế còn khá nhiều việc chúng ta cần làm khi cầu hình database server như xóa bỏ test database, đặt pasword cho root account của MariaDB, hạn chế các IP address có thể truy cập cổng 3306,...

    Reference: http://docs.ansible.com/ansible/latest/list_of_database_modules.html#mysql

    Manage users and groups.

    Công việc quản lý usersgroups sử dụng ad-hoc command là khá phổ biến với Ansible và có thể được thực hiện khá đơn giản sử dụng usergroup module được cung cấp bởi Ansible. Sử dụng hai module trên việc quản lý user và group sẽ là như nhau trên tất cả các Linux Distribution được hỗ trợ.

    Hãy cùng thêm một nhóm tên admin cho các servers bên trong nhóm app mà chúng ta đã định nghĩa trong inventory file.

    ansible -i inventory.ini app -b -m group -a "name=admin state=present"
    

    Let's break it down!

    • -b: thực hiện command dưới quyền sudo
    • -m group: sử dụng group module
    • -a "name=admin state=present": tạo một normal group với tên là admin

    Kết quả tra về trong lần chạy đầu tiên:

    192.168.60.5 | SUCCESS => {
        "changed": true, 
        "gid": 1002, 
        "name": "admin", 
        "state": "present", 
        "system": false
    }
    192.168.60.4 | SUCCESS => {
        "changed": true, 
        "gid": 1002, 
        "name": "admin", 
        "state": "present", 
        "system": false
    }
    

    Để ý rằng giá trị changed được đặt là true, tức là đã có thay đổi trên các servers. Ngoài ra chúng ta có thêm một số thông tin về group vừa tạo. Chạy lại command trên nhiều lần, giá trị changed sẽ là false do group đã tồn tại và Ansible đã kiểm tra được điều đó - idempotence Remember?

    Để xóa một group chúng ta sẽ đặt stateabsent, tạo một system group bằng cách đặt systemyes, thay đổi group ID sử dụng gid. Các bạn có thể tham khảo link bên dưới để rõ hơn.

    Reference: http://docs.ansible.com/ansible/latest/group_module.html

    Tiếp theo hãy cùng thêm một user với tên là vinhnguyen vào group admin mà chúng ta vừa tạo.

    user module khá phức tạp với rất nhiều options khác nhau (bạn có thểm tham khảo reference bên dưới). Tuy nhiên để thực hiện công việc trên, chúng ta sẽ sử dụng command đơn giản sau:

    ansible -i inventory.ini app -b -m user -a "name=vinhnguyen group=admin createhome=yes"
    

    Command trên khá dễ hiểu, createhome option chỉ định việc tạo home directory cho user hay không? Ngoài ra có một số option khác thường được sử dụng như:

    • shell: cài đặt shell cho tài khoản
    • generate_ssh_key: Tạo SSH key cho tài khoản
    • pasword: cài đặt password cho tài khoản

    Để xóa một user chúng ta có thể sử dụng command sau:

    ansible -i inventory.ini app -b -m user -a "name=vinhnguyen state=absent remove=yes"
    

    Kết quả trả về sẽ như sau:

    192.168.60.5 | SUCCESS => {
        "changed": true, 
        "force": false, 
        "name": "vinhnguyen", 
        "remove": true, 
        "state": "absent", 
        "stderr": "userdel: vinhnguyen mail spool (/var/mail/vinhnguyen) not found\n", 
        "stderr_lines": [
            "userdel: vinhnguyen mail spool (/var/mail/vinhnguyen) not found"
        ]
    }
    192.168.60.4 | SUCCESS => {
        "changed": true, 
        "force": false, 
        "name": "vinhnguyen", 
        "remove": true, 
        "state": "absent", 
        "stderr": "userdel: vinhnguyen mail spool (/var/mail/vinhnguyen) not found\n", 
        "stderr_lines": [
            "userdel: vinhnguyen mail spool (/var/mail/vinhnguyen) not found"
        ]
    }
    

    Ansible command trên tương tự với việc sử dụng userdel --remove shell command.

    Keep digging!

    Reference: http://docs.ansible.com/ansible/latest/user_module.html

    Manage packages.

    Để cài đặt phần mềm cho các servers, chúng ta có thể sử dụng apt module (do OS chúng ta đang dùng là Debian - Ubuntu). Nếu servers bạn đang sử dụng là CentOS, yum module sẽ được sử dụng. Danh sách các module dùng để quản lý các package theo OS có thể tham khảo tại đây

    Chúng ta sẽ lấy một ví dụ đơn giản đó là cài đặt git trên các application servers. Chúng ta có thể sử dụng command sau:

    ansible -i inventory.ini app -b -m apt -a "name=git state=present"
    

    Thông thường git đã được cài đặt sẵn, nên kết quả trả về sẽ như sau:

    192.168.60.4 | SUCCESS => {
        "cache_update_time": 1519711692, 
        "cache_updated": false, 
        "changed": false
    }
    192.168.60.5 | SUCCESS => {
        "cache_update_time": 1519711691, 
        "cache_updated": false, 
        "changed": false
    }
    

    apt module còn rất nhiều options khác, bạn có thể tham khảo trong documentation của Ansible.

    Chúng ta cũng có thể sử dụng package module để cài đặt một gói package trên mọi nền tảng Unix-like như Debian, RHEL, Fedora, Ubuntu, CentOS, FreeBSD,...

    ansible -i inventory.ini app -b -m package -a "name=git state=present"
    

    Ansible cũng có thể được sử dụng để quản lý các gói package trên nhiều nền tảng khác nhau, mà ở đó tên của package có thể khác nhau (chúng ta sẽ đề cập đến vấn đề này trong những bài viết sau).

    References:

    Manage files and directories.

    Quản lý tệp và thư mục cũng có thể được thực hiện bằng các ad-hoc command. Chúng ta sẽ đi qua một số use-cases cơ bản:

    Để lấy ra thông tin của một file (hoặc thư mục) trên hệ thống, chúng ta có thể sử dụng stat module, cụ thể:

    ansible -i inventory.ini 192.168.60.5 -m stat -a "path=/var/log/auth.log"
    

    Dữ liệu trả về sẽ ở dạng JSON như sau:

    192.168.60.5 | SUCCESS => {
        "changed": false, 
        "stat": {
            "atime": 1519712468.176912, 
            "attr_flags": "e", 
            "attributes": [
                "extents"
            ], 
            "block_size": 4096, 
            "blocks": 56, 
            "charset": "us-ascii", 
            "checksum": "fe4e52d3fd0e2046c5ab3dcb4ecb4427f60e92b9", 
            "ctime": 1519712656.904912, 
            "dev": 64512, 
            "device_type": 0, 
            "executable": false, 
            "exists": true, 
            "gid": 4, 
            "gr_name": "adm", 
            "inode": 524311, 
            "isblk": false, 
            "ischr": false, 
            "isdir": false, 
            "isfifo": false, 
            "isgid": false, 
            "islnk": false, 
            "isreg": true, 
            "issock": false, 
            "isuid": false, 
            "md5": "3a17a9dd1220a2763474319cd4b0b16f", 
            "mimetype": "text/plain", 
            ...
        }
    }
    

    Có rất nhiều thông tin được trả về, chúng ta có thể sử dụng các thông tin đó như là một điều kiện để thực hiện một tác vụ nào đó trong playbook.

    Reference: http://docs.ansible.com/ansible/latest/stat_module.html

    Để sao chép file hoặc thư mục lên server, chúng ta thường sử dụng scp hoặc rsync command. Ansible cung cấp các module có chức năng tương tự hai command trên. Một module khá phổ biến là copy.

    Giả sử chúng ta muốn sao chép file /etc/hosts lên trên các applications tại thư mục là /tmp/hosts, chúng ta có thể sử dụng command sau:

    ansible -i inventory.ini app -m copy -a "src=/etc/hosts dest=/tmp/hosts"
    

    Kết quả trả về sẽ tương tự như sau:

    192.168.60.4 | SUCCESS => {
        "changed": true, 
        "checksum": "381d97da2b3c021509e42d0ae7db1bf13baa6357", 
        "dest": "/tmp/hosts", 
        "gid": 1000, 
        "group": "vagrant", 
        "md5sum": "e97de1f6064fad82cb584925817347a7", 
        "mode": "0664", 
        "owner": "vagrant", 
        "size": 385, 
        "src": "/home/vagrant/.ansible/tmp/ansible-tmp-1519713281.56-123746043234602/source", 
        "state": "file", 
        "uid": 1000
    }
    ...
    

    Trong command trên, src có thể là một file hoặc một thư mục. Nếu giá trị của src có chứa trailing slash, nội dung của thư mục sẽ được sao chép đến dest. Ngược lại thì nội dung và cả thư mục gốc sẽ được sao chép đến dest.

    copy module sẽ phù hợp khi sao chép các file riêng lẻ hoặc các thư mục có kích thước nhỏ. Nếu số lượng và kích thước file hoặc thư mục lớn, chúng ta nên sử dụng một số module khác của Ansible như unarchive, synchronize hay rsync. Các bạn có thể tìm hiểu thêm trong documentation của Ansible để hiểu rõ hơn về các module đó.

    Reference: http://docs.ansible.com/ansible/latest/copy_module.html

    Để tạo mới file hoặc thư mục, chúng ta sẽ sử dụng file module. Sau đây là một số ví dụ minh họa:

    • Tạo mới một thư mục với đường dẫn là /etc/testing với bit mode là 644:
    ansible -i inventory.ini app -m file -a "dest=/tmp/testing mode=644 state=directory"
    

    Output cho lần chạy đầu tiên sẽ như sau:

    192.168.60.4 | SUCCESS => {
        "changed": true, 
        "gid": 1000, 
        "group": "vagrant", 
        "mode": "0644", 
        "owner": "vagrant", 
        "path": "/tmp/testing", 
        "size": 4096, 
        "state": "directory", 
        "uid": 1000
    }
    ...
    

    Tạo symbolic link:

    ansible -i inventory.ini \
        app -b \
        -m file \
        -a "src=/etc/environment dest=/tmp/environemnt owner=root group=root state=link"
    

    Chú ý state ở đây là link, chúng ta có thể tạo hard link với giá trị của statehard.

    Để xóa file hoặc thư mục chúng ta sẽ vẫn sử dụng file module cùng với đường dẫn đến file hoặc thư mục đó và giá trị của state sẽ là absent:

    ansible -i inventory.ini app -m file -a "dest=/tmp/testing state=absent"
    

    Reference: http://docs.ansible.com/ansible/latest/file_module.html

    Check log files.

    Kiểm tra file log là một công việc thường xuyên, nhất là khi chúng ta muốn debug application. Một số shell command thường được sử dụng cho công việc này là tail, cat, grep. Tuy nhiên có một số lưu ý:

    • Các command dùng để monitor hay tracking file sẽ không hoạt động với Ansible, ví dụ như tail -f chẳng hạn. Lý dó là Ansible chỉ hiển thị output sau khi tác vụ đã hoàn thành.
    • Không nên sử dụng cat để hiển thị nội dung của các file log có kích thước lơn. Cách tốt nhất là SSH vào server cần debug và kiểm tra.
    • Nếu chúng ta cần lưu hoặc lọc các nội dung cần thiết từ output của một command nào đó, sử dụng shell module thay vì command module. Hai module này có chức năng tương tự nhau, tuy nhiên shell module sẽ thực thi command trên một shell nào đó /bin/sh hay /bin/bash chẳng hạn. Chúng ta có thể chỉ định shell cần sử dụng thông qua executable option của shell module.

    Để hiển thị nội dung gần nhất của một log file, chúng ta có thể sử dụng:

    ansible -i inventory.ini app -b -a "tail /var/log/auth.log"
    ansible -i inventory.ini app -b -m command -a "tail /var/log/auth.log"
    

    Mặc định command module sẽ được sử dụng.

    Nếu muốn lọc output của comnand, chúng ta sẽ sử dụng shell module. Giả sử chúng ta muốn tìm số lần ansible xuất hiện trong file /var/log/auth.log (just fo fun!):

    ansible -i inventory.ini app -m shell -a "cat /var/log/auth.log | grep ansible | wc -l"
    

    Output của command trên sẽ tương tự như sau:

    192.168.60.4 | SUCCESS | rc=0 >>
    20
    
    192.168.60.5 | SUCCESS | rc=0 >>
    19
    

    References:

    Cron jobs.

    Quản lý cron jobs cũng là một công việc mà sysadmins thường xuyên phải thực hiện. Ansible cũng cung cấp cho chúng ta cron module với mục đích đó. Thay vì sử dụng crontab command, chúng ta có thể sử dụng cron module của Ansible. Module này cung cấp cho chúng ta khá nhiều options giúp cho việc định nghĩa chu kỳ của một job nào đó dễ dàng hơn. Dưới đây là một ví dụ đơn giản:

    ansible -i inventory.ini app -b -m cron \
        -a "name='example-daily-job' hour=22 job='/path/to/job.sh'"
    

    Trong ví dụ trên, cron job của chúng ta được định nghĩa trong job.sh sẽ được chạy vào 10h tối hàng ngày. Các option như day, hour, minute, month, weekday sẽ có giá trị mặc định là *. Ngoài ra Ansible còn cung cấp cho chúng ta một số giá trị đặc biệt thông qua việc sử dụng special_time option với các giá trị như reboot, yearly, annually, monthly, weekly, daily, hourly.

    Hơn nữa chúng ta có thể cài đặt user mà job sẽ được thực hiện sử dụng user option và tạo backup sử dụng backup option.

    Để xoa bỏ một cron job chúng ta sẽ đặt giá trị của state option là absent:

    ansible -i inventory.ini app -b -m cron -a "name='example-daily-job' state=absent"
    

    Reference: http://docs.ansible.com/ansible/latest/cron_module.html

    Limiting hosts.

    Trong hầu hết các ví dụ trong các phần trước, Ansible command được trên một nhóm các hosts (appdb). Tuy nhiên trên thực tế, đôi lúc chúng ta chỉ muốn thực hiện một số tác vụ trên một số hosts nhất định trong một group nào đó. Với Ansible chúng ta có thể sử dụng --limit option khi thực thi ansible command. Một ví dụ đơn giản là nếu chúng ta muốn khởi động lại ntp service trên application server đầu tiên trong app group:

    ansible -i inventory.ini app -b \
        -m service \
        -a "name=ntp state=restarted" --limit "192.168.60.4"
    

    Ở đây chúng ta sử dụng IP address để định danh các host tuy nhiên chúng ta hoàn toàn có thể sử dụng hostnames.

    --limit option không chỉ giới hạn ở việc sử dụng một string đơn giản, chúng ta có thể sử dụng wildcardregular expressions.

    Giả sử có rất nhiều hosts trong app group và chúng ta chỉ muốn khởi động lại ntp service cho các hosts có IP address kết thúc bằng 4. Sử dụng wildcard - * như sau:

    ansible -i inventory.ini app -b -m service -a "name=ntp state=restarted" --limit "*.4"
    

    Chúng ta cũng có thể sử dụng regular expression để thực hiện công việc trên, cụ thể như sau:

    ansible -i inventory.ini app -b -m service -a "name=ntp state=restarted" --limit ~".*\.4"
    

    Để ý dấu ~ trước khi bắt đầu regular expression.

    More !?

    Một số ứng dụng khác của Ansible ad-hoc command, các bạn có thể tham khảo thêm trong documentation:


    Chúng ta có thể sử dụng stupid shell command sau để lấy ra các Ansible command đã thực hiện từ đầu bài viết đến giờ.

    history | sed 's/.[ ]*.[0-9]*.[ ]*//' \
            | sed 's/[0-9]*-[0-9]*-[0-9]*[ ][0-9]*:[0-9]*[ ]*//' \
            | sort \
            | uniq \
            | grep "^ansible" \
            > commands.txt
    

    :cyclone: :cyclone: :cyclone:

    Conclusion

    Trong bài viết này chúng ta đã tìm hiểu tổng quan về Ansible, lý do nó là một công cụ khá phổ biến hiện nay. Tìm hiểu cách sử dụng Vagrant để cài đặt các local servers và sử dụng Ansible trên các servers đó. Phần cuối của bài viết có đề cập đến việc sử dụng các ad-hoc commands trong Ansible. Mục đích ở đây là để làm quen với Ansible và những công việc mà Ansible có thể thực hiện qua một số ví dụ đơn giản. Khi sử dụng Ansible trong thực tế chúng ta sẽ sử dụng playbook thay vì ad-hoc commands, và mọi thứ sẽ phức tạp hơn một chút thay vì thực thi một số commands khá đơn giản như ở trên. Trong bài viết tiếp theo mình sẽ trình bày về Ansible playbook - một khía cạnh trung tâm trong Ansible.

    What next?

    Trong các bài viết tiếp theo trong series về Ansible Fundamentals này, mình sẽ đề cập đến một số phần khá quan trọng trong Ansible. Dưới đây là một số chủ đề mà mình vừa nghĩ ra:

    • :white_check_mark: Ad-hoc Commands
    • :negative_squared_cross_mark: Ansible Playbooks.
    • :negative_squared_cross_mark: Ansible Inventories
    • :negative_squared_cross_mark: Ansible Roles and Includes
    • :negative_squared_cross_mark: Ansible and Deployment
    • :negative_squared_cross_mark: Ansible and Docker
    • :negative_squared_cross_mark: Ansible and Server Security
    • :negative_squared_cross_mark: Ansible and Network Configuration
    • ...

    Chém gió vậy thôi chứ viết được hết tất cả những vấn đề trên chắc cũng mất kha khá thời gian. Hy vọng nhưng bài viết của mình sẽ giúp ích một phần nhỏ nào đó cho các bạn. Peace!

    P/S: Bài viết có thể có nhiều sai sót, mong nhận được sự góp ý từ mọi người :smiley:
    Nguồn: Viblo



Có vẻ như bạn đã mất kết nối tới LaptrinhX, vui lòng đợi một lúc để chúng tôi thử kết nối lại.