← index

透過 Ansible playbook 來手動安裝 Docker

Manual installation Docker by Ansible playbooks on GCE.

在前一篇「在 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

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

$ ssh jiepeng@123.123.1.10

$ jiepeng@server:~$ 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