透過 Ansible playbook 來手動安裝 Docker

Apr 22, 2020

在前一篇「在 GCP 上執行你第一個 Ansible Ad-Hoc Command」 學到如何透過 ad-hoc 的方式來操作 Ansible, 這篇文章希望透過 Ansible playbook 來更有效的組織、自動化你的流程,會以一個安裝 docker 的方式作為範例來撰寫 playbook。

建立第一個 ansible playbook

以下面是一個簡單的 playbook 結構:

---
- hosts: server
become: true
tasks:
- name: Test Sever Connection
ping:
  • hosts:是你 inventory 內中的主機,可能一台或是某個群組
  • become:這裡設定為 true 預設會以 root 權限執行,也可以透過 become_user 來指定其他使用者
  • tasks:一系列要做的任務,每個 tasks 下面可能會有多個子 task
  • name & pingname 這裡指的是這個 task 的名稱,而 ping 則是 playbook module 中所提供的一個模組

接著請開一個新的資料夾叫做 ansible-playbook-example,並建立你的 inventory 和一個 main.yml, 接著把上面的 YAML 內容貼入到 main.yml

$ mkdir ansible-playbook-example
$ cd ansible-playbook-example
$ touch inventory main.yml

接著透過 --syntax-check 檢查你的 playbook 內的語法是否有錯誤:

$ ansible-playbook -i inventory main.yml --syntax-check

如果正確不會有錯誤的訊息,假設我今天 YAML 的縮排沒有寫好

ERROR! 'ping' is not a valid attribute for a Play
The error appears to be in '/Users/jiepeng/neighborhood999/ansible-playbook-example/main.yml': line 2, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
---
- hosts: pingserver
^ here

可以看到 ping 的欄位有問題。在執行 playbook 的時候,就會自動幫你做檢查了,這裡只是想提到有這個用法。

最後執行 playbook 來 ping 看看遠端的機器:

$ ansible-playbook -i inventory main.yml
PLAY [pingserver] ******************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************
ok: [123.123.1.10]
TASK [Test Sever Connection] *******************************************************************************************
ok: [123.123.1.10]
PLAY RECAP *************************************************************************************************************
123.123.1.10 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

成功!

透過 Ansible playbook 手動安裝 Docker

由於我在 GCE 上所開的機器的系統是 Ubuntu,所以這裡的安裝範例會以官方所提供的 Ubuntu 來示範。

延續上面的 main.yml 內容,繼續撰寫 playbook:

---
- hosts: server
become: true
tasks:
- name: Test Sever Connection
ping:

Installing Required Packages

在安裝 docker 之前,有一些套件是必需要用到的:

  • apt-transport-https
  • ca-certificates
  • curl
  • gnupg-agent
  • software-properties-common

這些套件可以透過 apt-get install 來安裝,但要把這些東西直接寫死在 playbook 內好像不太好,未來如果要新增其它套件時,修改起來比較不方便。

Ansible 提供了 var_files 這個欄位,可以讓你來 include 你所定義的 variables 檔案,這裡讓我們建立一個 vars.yml,內容如下:

requried_packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg-agent
- software-properties-common

required_packages 就是變數名稱,它可以被用在 playbook,內容就是安裝 docker 前所需要安裝的套件,實際上它就是一個陣列,也可以寫成:

requried_packages: [apt-transport-https, ca-certificates, curl, gnupg-agent, software-properties-common]

看個人習慣,這邊就以前者作為範例。

回到 main.yml 新增安裝套件的 task(綠色部分,紅色請忽略 😂):

---
- hosts: server
become: true
tasks:
- name: Test Sever Connection
ping:
+
+ - name: Install required packages
+ apt:
+ name: "{{ item }}"
+ state: latest
+ update_cache: true
+ loop: "{{ requried_packages }}"

這裡使用了 apt module,並使用了 loop 來迭代 requried_packages 內的參數, "{{ required_packages }}" 是 Jinja2 templating 語法,透過 {{ }} 來存取變數,所以這裡的 task 就會根據我們所提供的內容進行安裝:

$ ansible-playbook -i inventory main.yml
PLAY [pingserver] *************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************
ok: [123.123.1.10]
TASK [Test Connection] *************************************************************************************************
ok: [123.123.1.10]
TASK [Install required packages] ***************************************************************************************
changed: [123.123.1.10] => (item=apt-transport-https)
ok: [123.123.1.10] => (item=ca-certificates)
ok: [123.123.1.10] => (item=curl)
changed: [123.123.1.10] => (item=gnupg-agent)
ok: [123.123.1.10] => (item=software-properties-common)
PLAY RECAP *************************************************************************************************************
123.123.1.10 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

安裝成功!

從輸出可以看到 Test Connection 的內容沒有改變,狀態是 ok,而 Install required packages 是第一次執行 package 安裝,有些狀態是 changed,原因是系統內是沒有這些 package,所以會進行安裝,算是更動。

當安裝完畢後,再執行一次 playbook 而不做改變可以注意到 Install required packages 全部都是 ok

Add Docker's Official GPG Key

透過 apt_key module 來加入 GPG Key:

---
- hosts: server
become: true
tasks:
- name: Test Sever Connection
ping:
- name: Install required packages
apt:
name: "{{ item }}"
state: latest
update_cache: true
loop: "{{ requried_packages }}"
+
+ - name: Add docker's official GPG key
+ apt_key:
+ url: https://download.docker.com/linux/ubuntu/gpg
+ state: present

Add Docker Repository

透過 apt_repository 把 docker 的 repository 加入到你的 ppa:

- name: Add docker repository
apt_repository:
repo: deb https://download.docker.com/linux/ubuntu bionic stable
state: present

Install Docker Engine

Docker engine 這部分也有幾個東西要安裝,就像前面安裝 Ubuntu 套件一樣,在 vars.yml 內新增一個 docker_engines 變數,內容是安裝 docker engine 的東西:

vars.yml

requried_packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg-agent
- software-properties-common
docker_engines:
- docker-ce
- docker-ce-cli
- containerd.io

同樣透過 loop 的方式來依序安裝我們需要的套件:

main.yml

- name: Update apt cache and install docker engine
apt:
name: "{{ item }}"
state: latest
update_cache: true
loop: "{{ docker_engines }}"

完成以上的步驟後,你的 main.yml 會長這樣:

---
- hosts: server
become: true
vars_files:
- vars.yml
tasks:
- name: Test Connection
ping:
- name: Install required packages
apt:
name: "{{ item }}"
state: latest
update_cache: true
loop: "{{ requried_packages }}"
- name: Add docker's official GPG key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Add docker repository
apt_repository:
repo: deb https://download.docker.com/linux/ubuntu bionic stable
state: present
- name: Update apt cache and install docker engine
apt:
name: "{{ item }}"
state: latest
update_cache: true
loop: "{{ docker_engines }}"

Verify Docker

安裝完 docker 後,驗證一下是否正確安裝,執行一個 hello-world docker container 做測試:

- name: Verify docker installed
become: true
command: docker run --rm hello-world

在這邊執行一次 playbook 看一下結果:

$ ansible-playbook -i inventory main.yml
PLAY [server] **********************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************
ok: [123.123.1.10]
TASK [Test Connection] *************************************************************************************************
ok: [123.123.1.10]
TASK [Install required packages] ***************************************************************************************
ok: [123.123.1.10] => (item=apt-transport-https)
ok: [123.123.1.10] => (item=ca-certificates)
ok: [123.123.1.10] => (item=curl)
ok: [123.123.1.10] => (item=gnupg-agent)
ok: [123.123.1.10] => (item=software-properties-common)
TASK [Add docker's official GPG key] ***********************************************************************************
changed: [123.123.1.10]
TASK [Add docker repository] *******************************************************************************************
changed: [123.123.1.10]
TASK [Update apt cache and install docker engine] **********************************************************************
changed: [123.123.1.10] => (item=docker-ce)
ok: [123.123.1.10] => (item=docker-ce-cli)
ok: [123.123.1.10] => (item=containerd.io)
TASK [Verify docker installed] *****************************************************************************************
changed: [123.123.1.10]
PLAY RECAP *************************************************************************************************************
123.123.1.10 : ok=8 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Add User To Docker Group

完成上述步驟後,現在進入遠端伺服器進行操作試試看:

$ [email protected]:~$ docker ps
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/containers/json: dial unix /var/run/docker.sock: connect: permission denied

代表你權限不足所以沒辦法執行 docker 指令,這時候加上 sudo docker [command] 就可以了。

由於一開始就是以管理員的身份來做這一連串的操作(become: true),所以若要執行 docker 必須要加上 sudo docker command 才可以執行, 為了讓方便讓其他使用者也可以用,把這個使用者加入 docker 的 group,直接操作可以透過 sudo usermod -aG docker [username],但這裡讓我們透過 playbook 來完成這件事。

首先檢查一下 docker group 存不存在(有點多此一舉),接著把使用者加入 docker 這個 group,{{ user }} 則是在 vars.yml 內新增的變數。

- name: Check docker group is exists
group:
name: docker
state: present
- name: Add user to docker group
user:
name: "{{ user }}"
group: docker

執行:

$ ansible-playbook -i inventory main.yml
PLAY [server] **********************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************
ok: [123.123.1.10]
TASK [Test Connection] *************************************************************************************************
ok: [123.123.1.10]
TASK [Install required packages] ***************************************************************************************
ok: [123.123.1.10] => (item=apt-transport-https)
ok: [123.123.1.10] => (item=ca-certificates)
ok: [123.123.1.10] => (item=curl)
ok: [123.123.1.10] => (item=gnupg-agent)
ok: [123.123.1.10] => (item=software-properties-common)
TASK [Add docker's official GPG key] ***********************************************************************************
ok: [123.123.1.10]
TASK [Add docker repository] *******************************************************************************************
ok: [123.123.1.10]
TASK [Update apt cache and install docker engine] **********************************************************************
ok: [123.123.1.10] => (item=docker-ce)
ok: [123.123.1.10] => (item=docker-ce-cli)
ok: [123.123.1.10] => (item=containerd.io)
TASK [Verify docker installed] *****************************************************************************************
changed: [123.123.1.10]
TASK [Check docker group is exists] ************************************************************************************
ok: [123.123.1.10]
TASK [Add user to docker group] ****************************************************************************************
changed: [123.123.1.10]
PLAY RECAP *************************************************************************************************************
123.123.1.10 : ok=9 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

注意到 TASK [Add user to docker group] 部分的狀態是 changed,代表使用者有被加入到 docker group 了,再重新 ssh 登入伺服器測試後就可以成功直接執行 docker 指令了。

完整範例

main.yml

---
- hosts: server
become: true
vars_files:
- vars.yml
tasks:
- name: Test Connection
ping:
- name: Install required packages
apt:
name: "{{ item }}"
state: latest
update_cache: true
loop: "{{ requried_packages }}"
- name: Add docker's official GPG key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Add docker repository
apt_repository:
repo: deb https://download.docker.com/linux/ubuntu bionic stable
state: present
- name: Update apt cache and install docker engine
apt:
name: "{{ item }}"
state: latest
update_cache: true
loop: "{{ docker_engines }}"
- name: Verify docker installed
become: true
command: docker run --rm hello-world
- name: Check docker group is exists
group:
name: docker
state: present
- name: Add user to docker group
user:
name: "{{ user }}"
group: docker

vars.yml

user: [your-username]
requried_packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg-agent
- software-properties-common
docker_engines:
- docker-ce
- docker-ce-cli
- containerd.io

希望透過這個簡單的 docker 安裝範例讓你可以更了解 Ansible playbook。

Reference