這是一篇關于 ansible 的速成課程,你可以用作小項目的模闆,或者幫你深入了解這個神奇的工具。閱讀了本指南之後,你将對自動化伺服器配置、部署等有足夠的了解。
<a target="_blank"></a>
ansible 簡單的說是一個配置管理系統configuration management system。你隻需要可以使用 ssh 通路你的伺服器或裝置就行。它也不同于其他工具,因為它使用推送的方式,而不是像 puppet 或 chef 那樣使用拉取的方式。你可以将代碼部署到任意數量的伺服器上,配置網絡裝置或在基礎架構中自動執行任何操作。
假設你使用 mac 或 linux 作為你的工作站,ubuntu trusty 作為你的伺服器,并有一些安裝軟體包的經驗。此外,你的計算機上将需要以下軟體。是以,如果你還沒有它們,請先安裝:
<a href="https://www.virtualbox.org/" target="_blank">virtualbox</a>
<a href="https://www.vagrantup.com/downloads.html" target="_blank">vagrant</a>
我們将模拟 2 個連接配接到 mysql 資料庫的 web 應用程式伺服器。web 應用程式使用 rails 5 和 puma。
為這個項目建立一個檔案夾,并将下面的内容儲存到名為 <code>vagrantfile</code> 的檔案。
<code>vms = [</code>
<code>[ "web1", "10.1.1.11"],</code>
<code>[ "web2", "10.1.1.12"],</code>
<code>[ "dbserver", "10.1.1.21"],</code>
<code>]</code>
<code></code>
<code>vagrant.configure(2) do |config|</code>
<code>vms.each { |vm|</code>
<code>config.vm.define vm[0] do |box|</code>
<code>box.vm.box = "ubuntu/trusty64"</code>
<code>box.vm.network "private_network", ip: vm[1]</code>
<code>box.vm.hostname = vm[0]</code>
<code>box.vm.provider "virtualbox" do |vb|</code>
<code>vb.memory = "512"</code>
<code>end</code>
<code>}</code>
我們希望我們的虛拟機能互互相動,但不要讓流量流出到真實的網絡,是以我們将在 virtualbox 中建立一個僅主機(host-only)的網絡擴充卡。
打開 virtualbox
轉到 preferences
轉到 network
單擊 host-only
單擊添加網絡
單擊 adapter
将 ipv4 設定為 <code>10.1.1.1</code>,ipv4 網絡掩碼:<code>255.255.255.0</code>
單擊 “ok”
在終端中,在存放 <code>vagrantfile</code> 的項目目錄中,輸入下面的指令:
<code>vagrant up</code>
它會建立你的虛拟機,是以會花費一會時間。輸入下面的指令并驗證輸出内容以檢查是否已經工作:
<code>$ vagrant status</code>
<code>current machine states:</code>
<code>web1 running (virtualbox)</code>
<code>web2 running (virtualbox)</code>
<code>master running (virtualbox)</code>
<code>this environment represents multiple vms. the vms are all listed</code>
<code>above with their current state. for more information about a specific</code>
<code>vm, run `vagrant status name`.</code>
現在使用 <code>vagrant</code> 的使用者名和密碼 ,按 <code>vagrantfile</code> 中的 ip 登入其中一台虛拟機,這将驗證虛拟機并将它們的密鑰添加到你的已知主機(<code>known_hosts</code>)檔案中。
<code>ssh [email protected] # password is `vagrant`</code>
<code>ssh [email protected]</code>
<code>ssh [email protected]</code>
恭喜你!現在你已經有可以實驗的伺服器了。下面的剩下的部分!
對于 mac 使用者:
<code>$ brew install ansible</code>
對于 ubuntu 使用者:
<code>$ sudo apt install ansible</code>
確定你使用了ansible 最近的版本 2.1 或者更高的版本:
<code>$ ansible --version</code>
<code>ansible 2.1.1.0</code>
ansible 使用清單檔案來了解要使用的伺服器,以及如何将它們分組以并行執行任務。讓我們為這個項目建立我們的清單檔案 <code>inventory</code>,并将它放在與 <code>vagrantfile</code> 相同的檔案夾中:
<code>[all:children]</code>
<code>webs</code>
<code>db</code>
<code>[all:vars]</code>
<code>ansible_user=vagrant</code>
<code>ansible_ssh_pass=vagrant</code>
<code>[webs]</code>
<code>web1 ansible_host=10.1.1.11</code>
<code>web2 ansible_host=10.1.1.12</code>
<code>[db]</code>
<code>dbserver ansible_host=10.1.1.21</code>
<code>[all:children]</code> 定義一個組的組(<code>all</code>)
<code>[all:vars]</code> 定義屬于組 <code>all</code> 的變量
<code>[webs]</code> 定義一個組,就像 <code>[db]</code> 一樣
檔案的其餘部分隻是主機的聲明,帶有它們的名稱和 ip
空行表示聲明結束
現在我們有了一個清單,我們可以從指令行開始使用 ansible,指定一個主機或一個組來執行指令。以下是檢查與伺服器的連接配接的指令示例:
<code>$ ansible -i inventory all -m ping</code>
<code>-i</code> 指定清單檔案
<code>all</code> 指定要操作的伺服器或伺服器組
<code>-m' 指定一個 ansible 子產品,在這種情況下為</code>ping`
下面是指令輸出:
<code>dbserver | success => {</code>
<code>"changed": false,</code>
<code>"ping": "pong"</code>
<code>web1 | success => {</code>
<code>web2 | success => {</code>
伺服器以不同的順序響應,這隻取決于誰先響應,但是這個沒有關系,因為 ansible 獨立保持每台伺服器的狀态。
你也可以使用另外一個選項來運作任何指令:
<code>-a <command></code>
<code>$ ansible -i inventory all -a uptime</code>
<code>web1 | success | rc=0 >></code>
<code>21:43:27 up 25 min, 1 user, load average: 0.00, 0.01, 0.05</code>
<code>dbserver | success | rc=0 >></code>
<code>21:43:27 up 24 min, 1 user, load average: 0.00, 0.01, 0.05</code>
<code>web2 | success | rc=0 >></code>
這是隻有一台伺服器的另外一個例子:
<code>$ ansible -i inventory dbserver -a "df -h /"</code>
<code>filesystem size used avail use% mounted on</code>
<code>/dev/sda1 40g 1.4g 37g 4% /</code>
下面是一個運作 shell 指令的劇本示例,将其儲存為 <code>playbook1.yml</code>:
<code>---</code>
<code>- hosts: all</code>
<code>tasks:</code>
<code>- shell: uptime</code>
<code>---</code> 是 yaml 檔案的開始
<code>- hosts</code>:指定要使用的組
<code>tasks</code>:标記任務清單的開始
記住:yaml 需要縮進結構,確定你始終遵循劇本中的正确結構
用下面的指令運作它:
<code>$ ansible-playbook -i inventory playbook1.yml</code>
<code>play [all] *********************************************************************</code>
<code>task [setup] *******************************************************************</code>
<code>ok: [web1]</code>
<code>ok: [web2]</code>
<code>ok: [dbmaster]</code>
<code>task [command] *****************************************************************</code>
<code>changed: [web1]</code>
<code>changed: [web2]</code>
<code>changed: [dbmaster]</code>
<code>play recap *********************************************************************</code>
<code>dbmaster : ok=2 changed=1 unreachable=0 failed=0</code>
<code>web1 : ok=2 changed=1 unreachable=0 failed=0</code>
<code>web2 : ok=2 changed=1 unreachable=0 failed=0</code>
正如你所見,ansible 運作了 2 個任務,而不是隻有劇本中的一個。<code>task [setup]</code> 是一個隐式任務,它會首先運作以捕獲伺服器的資訊,如主機名、ip、發行版和更多詳細資訊,然後可以使用這些資訊運作條件任務。
還有最後的 <code>play recap</code>,其中 ansible 顯示了運作了多少個任務以及每個對應的狀态。在我們的例子中,因為我們運作了一個 shell 指令,ansible 不知道結果的狀态,它被認為是 <code>changed</code>。
<code>- hosts: webs</code>
<code>become_user: root</code>
<code>become: true</code>
<code>- apt: name=git state=present</code>
有一些語句可以應用于 ansible 中所有子產品;一個是 <code>name</code> 語句,可以讓我們輸出關于正在執行的任務的更具描述性的文本。要使用它,保持任務内容一樣,但是添加 <code>name :描述性文本</code> 作為第一行,是以我們以前的文本将改成:
<code>- name: this task will make sure git is present on the system</code>
<code>apt: name=git state=present</code>
當你要處理一個清單時,比如要安裝的項目和軟體包、要建立的檔案,可以用 ansible 提供的<code>with_items</code>。下面是我們如何在 <code>playbook3.yml</code> 中使用它,同時添加一些我們已經知道的其他語句:
<code>- name: installing dependencies</code>
<code>apt: name={{item}} state=present</code>
<code>with_items:</code>
<code>- git</code>
<code>- mysql-client</code>
<code>- libmysqlclient-dev</code>
<code>- build-essential</code>
<code>- python-software-properties</code>
<code>vars:</code>
<code>- secret_key: vqnzcldcv9a3jk</code>
<code>- path_to_vault: /opt/very/deep/path</code>
<code>- name: setting a configuration file using template</code>
<code>template: src=myconfig.j2 dest={{path_to_vault}}/app.conf</code>
正如你看到的,我可以使用 <code>{{path_to_vault}}</code> 作為劇本的一部分,但也因為我使用了 <code>template</code>語句,我可以使用 <code>myconfig.j2</code> 中的任何變量,該檔案必須存在一個名為 <code>templates</code> 的子檔案夾中。你項目樹應該如下所示:
<code>├── vagrantfile</code>
<code>├── inventory</code>
<code>├── playbook1.yml</code>
<code>├── playbook2.yml</code>
<code>└── templates</code>
<code>└── myconfig.j2</code>
當 ansible 找到一個 <code>template</code> 語句後它會在 <code>templates</code> 檔案夾内查找,并将把被 <code>{{</code> 和 <code>}}</code> 括起來的變量展開來。
示例模闆:
<code>this is just an example vault_dir: {{path_to_vault}} secret_password: {{secret_key}}</code>
即使你不擴充變量你也可以使用 <code>template</code>。考慮到将來會添加是以我先做了。比如建立一個<code>hosts.j2</code> 模闆并加入主機名和 ip。
<code>10.1.1.11 web1</code>
<code>10.1.1.12 web2</code>
<code>10.1.1.21 dbserver</code>
這裡要用像這樣的語句:
<code>- name: installing the hosts file in all servers</code>
<code>template: src=hosts.j2 dest=/etc/hosts mode=644</code>
你應該盡量使用子產品,因為 ansible 可以跟蹤任務的狀态,并避免不必要的重複,但有時 shell 指令是不可避免的。 對于這些情況,ansible 提供兩個選項:
<code>- name: 'run db:migrate'</code>
<code>shell: cd {{appdir}};rails db:migrate</code>
<code>run_once: true</code>
通過指定 <code>ignore_errors:true</code>,你可以運作可能會失敗的任務,但不會影響劇本中剩餘的任務完成。這是非常有用的,例如,當删除最初并不存在的日志檔案時。
<code>- name: 'delete logs'</code>
<code>shell: rm -f /var/log/nginx/errors.log</code>
<code>ignore_errors: true</code>
現在用我們先前學到的,這裡是每個檔案的最終版:
<code>vagrantfile</code>:
<code>inventory</code>:
<code>templates/hosts.j2</code>:
<code>templates/my.cnf.j2</code>:
<code>[client]</code>
<code>port = 3306</code>
<code>socket = /var/run/mysqld/mysqld.sock</code>
<code>[mysqld_safe]</code>
<code>nice = 0</code>
<code>[mysqld]</code>
<code>server-id = 1</code>
<code>user = mysql</code>
<code>pid-file = /var/run/mysqld/mysqld.pid</code>
<code>basedir = /usr</code>
<code>datadir = /var/lib/mysql</code>
<code>tmpdir = /tmp</code>
<code>lc-messages-dir = /usr/share/mysql</code>
<code>skip-external-locking</code>
<code>bind-address = 0.0.0.0</code>
<code>key_buffer = 16m</code>
<code>max_allowed_packet = 16m</code>
<code>thread_stack = 192k</code>
<code>thread_cache_size = 8</code>
<code>myisam-recover = backup</code>
<code>query_cache_limit = 1m</code>
<code>query_cache_size = 16m</code>
<code>log_error = /var/log/mysql/error.log</code>
<code>expire_logs_days = 10</code>
<code>max_binlog_size = 100m</code>
<code>[mysqldump]</code>
<code>quick</code>
<code>quote-names</code>
<code>[mysql]</code>
<code>[isamchk]</code>
<code>!includedir /etc/mysql/conf.d/</code>
<code>final-playbook.yml</code>:
<code>- name: 'install common software on all servers'</code>
<code>- name: 'install hosts file'</code>
<code>- hosts: db</code>
<code>- name: 'software for db server'</code>
<code>- mysql-server</code>
<code>- percona-xtrabackup</code>
<code>- mytop</code>
<code>- mysql-utilities</code>
<code>- name: 'mysql config file'</code>
<code>template: src=my.cnf.j2 dest=/etc/mysql/my.cnf</code>
<code>- name: 'restart mysql'</code>
<code>service: name=mysql state=restarted</code>
<code>- name: 'grant access to web app servers'</code>
<code>shell: echo 'grant all privileges on *.* to "root"@"%" with grant option;flush privileges;'|mysql -u root mysql</code>
<code>- appdir: /opt/dummyapp</code>
<code>- name: 'add ruby-ng repo'</code>
<code>apt_repository: repo='ppa:brightbox/ruby-ng'</code>
<code>- name: 'install rails software'</code>
<code>- ruby-dev</code>
<code>- ruby-all-dev</code>
<code>- ruby2.2</code>
<code>- ruby2.2-dev</code>
<code>- ruby-switch</code>
<code>- libcurl4-openssl-dev</code>
<code>- libssl-dev</code>
<code>- zlib1g-dev</code>
<code>- nodejs</code>
<code>- name: 'set ruby to 2.2'</code>
<code>shell: ruby-switch --set ruby2.2</code>
<code>- name: 'install gems'</code>
<code>shell: gem install bundler rails</code>
<code>- name: 'kill puma if running'</code>
<code>shell: file /run/puma.pid >/dev/null && kill `cat /run/puma.pid` 2>/dev/null</code>
<code>- name: 'clone app repo'</code>
<code>git:</code>
<code>repo=https://github.com/c0d5x/rails_dummyapp.git</code>
<code>dest={{appdir}}</code>
<code>version=staging</code>
<code>force=yes</code>
<code>- name: 'run bundler'</code>
<code>shell: cd {{appdir}};bundler</code>
<code>- name: 'run db:setup'</code>
<code>shell: cd {{appdir}};rails db:setup</code>
<code>- name: 'run rails server'</code>
<code>shell: cd {{appdir}};rails server -b 0.0.0.0 -p 80 --pid /run/puma.pid -d</code>
将這些檔案放在相同的目錄,運作下面的指令打開你的開發環境:
<code>ansible-playbook -i inventory final-playbook.yml</code>
確定修改了代碼并推送到了倉庫中。接下來,確定你 git 語句中使用了正确的分支:
作為一個例子,你可以修改 <code>version</code> 字段為 <code>master</code>,再次運作劇本:
檢查所有的 web 伺服器上的頁面是否已更改:<code>http://10.1.1.11</code> 或 <code>http://10.1.1.12</code>。将其更改為<code>version = staging</code> 并重新運作劇本并再次檢查頁面。
你還可以建立隻包含與部署相關的任務的替代劇本,以便其運作更快。
這隻是可以做的很小一部分。我們沒有接觸角色role、過濾器filter、調試等許多其他很棒的功能,但我希望它給了你一個良好的開始!是以,請繼續學習并使用它。
原文釋出時間為:2017-01-12
本文來自雲栖社群合作夥伴“linux中國”