筆者這裡不以 Docker 為名,而是以 Container 為名,因為 Docker 只是眾多容器技術中的一種,雖然目前最流行,但並非唯一選擇。
其他如 Podman、CRI-O 等也都是不錯的選擇。
使用 Ansible 管理容器化應用時,重點在於理解容器的概念和操作,而非侷限於某一特定技術。
在講解這篇文章之前,筆者從 macOS 被人詬病的 docker desktop 再到後面出的 orbstack,筆者都有使用過,但在遇到 Podman 之後,就毅然決然地將 docker 從系統中移除了,原因有以下幾點:
為什麼 Docker 很流行?因為它是第一個讓容器技術變得易於使用和普及的工具,所以不知不覺就變成了一個代名詞,但各位還是要深入了解容器技術的本質,這樣才能在不同的技術選擇中做出最適合自己的決定。
另外 Podman 在 4.4 版本的時候整合了 Quadlet,有興趣的人可以參考這篇文章
Ansible 官方有提供 Podman Module 可以使用。
剛剛有提到 Podman 有整合了 Quadlet,所以我們可以直接使用 systemd 來管理 Podman 的容器,這樣就可以很方便地將容器的啟動和停止納入系統服務的管理範疇。
---
- name: "拉取一個 traefik image"
containers.podman.podman_image:
# 大家都在介紹 nginx,但是還是有其他好用的,所以筆者這裡來分享 traefik 哈哈
name: "docker.io/library/traefik:v3.5.2"
pull: true
# 因為我們要用 Podman 的 systemd 整合,所以要將 application container 的服務單元產生出來
- name: "產生 application systemd 服務單元"
ansible.builtin.template:
src: application.template.j2
dest: "/home/deploy/.config/containers/systemd/application.container"
mode: "0644"
# 重新載入 systemd 配置並啟動 application container
# daemon_reload: true 確保新的配置檔被讀取
- name: "重新載入 systemd 並重啟 application 服務"
ansible.builtin.systemd:
name: "application"
# 這邊要注意哦,因為 podman 的特性,所以 systemd 服務要加上 scope: user
scope: user
state: restarted
daemon_reload: true
# 建立一個臨時的容器
- name: "建立臨時容器"
containers.podman.podman_container:
name: application_tmp
image: "application:latest"
state: present
# src 和 dest 的路徑要根據實際情況調整,因為筆者都先隨便打的
- name: "從容器內部複製檔案到 host 上"
containers.podman.podman_container_copy:
container: application_tmp
src: "/application/public/."
dest: "/home/deploy/application/public"
from_container: true
# 清理臨時容器,釋放資源
# state: absent 表示完全移除容器
- name: "移除臨時容器"
containers.podman.podman_container:
name: application_tmp
state: absent
# 取得 container 的資訊,確保 container 已經啟動
# retries 和 delay 用於 retry,確保容器有足夠時間重啟
- name: "等待 predeploy 容器啟動完成"
containers.podman.podman_container_info:
name: "application"
register: container_info
# retries * delay = 60s,因為每次間隔 1s,最多重試 60 次,所以我們就獲得了 60s 的等待時間
retries: "60"
delay: "1"
until: container_info.containers | length > 0 and container_info.containers[0].State.Status == 'running'
- name: "透過 container 執行一些任務,ex: pnpm install"
containers.podman.podman_container_exec:
name: "application"
command: bash -eo pipefail -c "cd /tmp && pnpm install"
# 剛剛介紹了 copy,這邊多補充個要如何 copy 多個 files
- name: "將多個檔案複製 host 主機內"
containers.podman.podman_container_copy:
container: "application"
src: "{{ item }}"
dest: "/home/deploy/application/shared"
from_container: true
loop:
- "/application/config/prod.yaml"
- "/application/config/stage.yaml"
- "/application/config/qa.yaml"
# 停止 application container
# 看到這,各位可能會想:不對啊!剛剛都用 podman module 在控制,啊為什麼這邊會用 systemd stop container,因為剛剛有提到 podman 具備跟 systemd 整合的特性,有這麼方便的特性當然要好好善加利用才對
- name: "停止 application container"
ansible.builtin.systemd:
name: "application"
scope: user
state: stopped
明天我們來學習一些故障排除與維運技巧