Ansible 作為常用的自動化工具,能有效簡化應用程式佈署流程。本文首先介紹 ansible-playbook 的基本執行方式,包括輸出解讀、遠端使用者設定、sudo 選項,以及其他常用選項如自訂清單檔案、詳細模式、變數定義、平行執行數量、連線型別和檢查模式。接著,以 Rocky Linux 系統佈署 Node.js 應用為例,示範如何撰寫 playbook 安裝 EPEL 和 Remi 儲存函式庫、匯入 GPG key、安裝 Node.js 和 npm,並組態防火牆開放 HTTP 服務。此外,文章也涵蓋了自動化佈署與持續整合(CI)的實踐,提供一個包含建置、測試和佈署階段的 CI Pipeline範例,並使用 Mermaid 語法圖示模擬節點間的通訊架構。最後,文章更進一步示範如何使用 Ansible 建立 Docker 環境,包含執行 Flask 應用程式的容器、MySQL 資料函式庫容器以及資料容器,並詳細說明 Vagrantfile 和 Ansible playbook 的撰寫、Docker image 建置、容器啟動與組態等步驟,提供一個完整且可擴充套件的 Docker 環境建置方案。
Ansible 實戰:自動化佈署 Node.js 應用
使用 ansible-playbook 執行 Playbook
執行 ansible-playbook 命令時,會顯示類別似以下的輸出:
playbook: playbook.yml
play #1 (all): host count=4
127.0.0.1
192.168.24.2
foo.example.com
bar.example.com
其中 count 是根據清單中定義的伺服器數量,後面跟著所有在清單中定義的主機列表。
設定遠端使用者與 Sudo 選項
若在 Playbook 中未明確定義 remote_user,Ansible 會假設使用清單檔案中特定主機的使用者名稱進行連線,如果沒有則回退到本地使用者名稱。可以使用 --user 或 -u 選項明確定義遠端使用者:
$ ansible-playbook playbook.yml --user=johndoe
在某些情況下,需要將 sudo 密碼傳遞到遠端伺服器以執行 sudo 命令。這時需要使用 --ask-become-pass 或 -K 選項。也可以使用 --become 或 -b 選項強制所有任務都以 sudo 執行。此外,可以使用 --become-user 選項定義 sudo 使用者:
$ ansible-playbook playbook.yml --become --become-user=janedoe --ask-become-pass
如果未使用金鑰驗證來連線伺服器,可以使用 --ask-pass 選項。
其他常用的 ansible-playbook 選項
--inventory=PATH或-i PATH:定義自訂的清單檔案路徑(預設是/etc/ansible/hosts)。--verbose或-v:啟用詳細模式(顯示所有輸出,包括成功的選項)。可以使用-vvvv取得更多細節。--extra-vars=VARS或-e VARS:定義在 Playbook 中使用的變數,格式為"key=value,key=value"。--forks=NUM或-f NUM:設定並發執行的伺服器數量(整數)。設定為高於5的數字以增加並發任務的伺服器數量。--connection=TYPE或-c TYPE:指定連線型別(預設為 ssh;有時可能需要使用 local 在本地機器或遠端伺服器上執行 Playbook)。--check: 在檢查模式(“Dry Run”)下執行 Playbook;所有在 Playbook 中定義的任務都會被檢查,但不會實際執行。
這些選項應該足夠開始在自己伺服器或虛擬機器上執行 Playbook。本章剩餘部分將介紹更真實的 Ansible Playbook。
案例研究:Rocky Linux Node.js 應用伺服器
安裝與組態 Node.js 應用
以下是一個簡單的 YAML Playbook 檔案(playbook.yml),用於組態 Rocky Linux 伺服器來執行 Node.js 應用:
---
- hosts: all
become: yes
vars:
node_apps_location: /usr/local/opt/node
tasks:
# 加入 EPEL 和 Remi 儲存函式庫
- name: Install EPEL repository
yum:
name: epel-release
state: present
- name: Import Remi GPG key
rpm_key:
key: https://rpms.remirepo.net/RPM-GPG-KEY-remi2018
state: present
- name: Add Remi repository
yum_repository:
name: remi
description: Remi's RPM repository for Enterprise Linux 8 - $basearch
baseurl: https://rpms.remirepo.net/enterprise/8/$basearch/
gpgcheck: yes
enabled: yes
- name: Install Node.js (npm and all its dependencies)
yum:
name: npm
state: present
enablerepo: epel
# 安裝 Node.js 應用並啟動服務(具體步驟省略)
內容解密:
- Install EPEL repository:此任務安裝 EPEL 儲存函式庫,這是 Red Hat Enterprise Linux 的額外軟體儲存函式庫。
- Import Remi GPG key:此任務匯入 Remi 儲存函式庫的 GPG 鑰匙,這是確保軟體包完整性和來源可靠性的一種方式。
- Add Remi repository:此任務新增 Remi 儲存函式庫,這個儲存函式庫提供了更新版本的 Node.js 和其他軟體包。
- Install Node.js (npm and all its dependencies):此任務安裝 npm 和其所有依賴專案。
這些步驟確保了系統上有最新版本的 Node.js,並且可以透過 npm 安裝和管理其他 JavaScript 模組。
其他組態與最佳實踐
在實際應用中,可能需要進一步組態防火牆、安全組、日誌記錄等方面。此外,還需要考慮到自動化佈署和持續整合(CI)流程。這些都是自動化維運中的重要環節。
防火牆組態
在安裝完 Node.js 應用後,通常需要組態防火牆以允許外部存取。以下是一個簡單的防火牆組態範例:
---
- hosts: all
become: yes
tasks:
- name: Open port for Node.js application
firewalld:
service: http
permanent: yes
state: enabled
- name: Reload firewalld to apply changes
service:
name: firewalld
state: reloaded
內容解密:
- Open port for Node.js application:此任務開啟 HTTP 專案來允許外部存取。
- Reload firewalld to apply changes:此任務重新載入防火牆以應用變更。
這樣可以確保 Node.js 應用可以被外部存取。
自動化佈署與持續整合(CI)
在現代 DevOps 工作流程中,自動化佈署和持續整合(CI)是不可或缺的一部分。以下是一個簡單的 CI Pipeline範例:
stages:
- build
- test
- deploy
build_job:
stage: build
script:
- echo "Building the application..."
- npm install
test_job:
stage: test
script:
- echo "Running tests..."
- npm test
deploy_job:
stage: deploy
script:
- echo "Deploying the application..."
- ansible-playbook deploy.yml --inventory inventory.ini --user ubuntu --private-key ~/.ssh/id_rsa --extra-vars "node_apps_location=/usr/local/opt/node"
內容解密:
- build_job:此階段負責構建應用程式,通常包括安裝依賴項。
- test_job:此階段負責執行測試來確保應用程式執行正常。
- deploy_job:此階段負責將應用程式佈署到生產環境。
這樣的Pipeline可以確保每次提交程式碼後都能自動構建、測試和佈署應用程式。
模擬節點間通訊圖表示意圖
graph TD;
A[Client] --> B[Load Balancer]
B --> C[Node Server]
B --> D[Node Server]
C --> E[Database]
D --> E[Database]
內容解密:
此圖示展示了客戶端透過負載平衡器存取兩個節點伺服器,這些節點伺服器再與分享資料函式庫進行互動。
建立 Flask 應用程式的 Docker 環境
接下來,玄貓將帶領讀者建立一個更實用的 Docker 環境。這個環境包含一個執行應用程式的容器(使用 Flask,一個輕量級的 Python 網頁框架),以及一個執行資料函式庫(MySQL)的容器,還有資料容器用來持久化 MySQL 資料函式庫。因為 MySQL 容器內部的資料會在容器停止時消失,所以需要一個獨立的資料容器來持久化這些資料。
Docker 環境的基本構建
首先,我們需要準備好 Vagrantfile 和 Ansible playbook。這些檔案將幫助我們在任何能夠執行 Ansible 和 Vagrant 的機器上進行測試。
Vagrantfile
首先,建立一個 docker 資料夾,並在其中建立 Vagrantfile:
# -*- mode: ruby -*-
# vi: set ft=ruby :
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "geerlingguy/ubuntu2004"
config.vm.network :private_network, ip: "192.168.33.39"
config.ssh.insert_key = false
config.vm.hostname = "docker-flask.test"
config.vm.provider :virtualbox do |v|
v.name = "docker-flask.test"
v.memory = 1024
v.cpus = 2
v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
v.customize ["modifyvm", :id, "--ioapic", "on"]
end
# Enable provisioning with Ansible.
config.vm.provision "ansible" do |ansible|
ansible.playbook = "provisioning/main.yml"
end
end
此處我們使用了 Ubuntu 作為主機系統,並指定了一個 Ansible playbook(provisioning/main.yml)來進行設定。
Ansible Playbook
接下來,我們需要在 provisioning 資料夾中建立 main.yml:
---
- hosts: all
become: true
vars:
build_root: /vagrant/provisioning
pre_tasks:
- name: Update apt cache if needed.
apt: update_cache=yes cache_valid_time=3600
roles:
- role: geerlingguy.docker
tasks:
- import_tasks: setup.yml
- import_tasks: docker.yml
在此 Playbook 中,我們使用了 become 標籤來確保所有任務都以 root 許可權執行。這是因為 Docker 需要 root 許可權或使用者必須在 docker 裝置群組中才能執行命令。我們還設定了一個 build_root 論據變數,這將用於告知 Docker 在虛擬機器內建構容器。
我們使用的是 geerlingguy.docker role,這個 role 不需要額外設定或組態,但必須使用 ansible-galaxy 安裝。建立 requirements.yml:
roles:
- name: geerlingguy.docker
安裝 role:
$ ansible-galaxy install -r requirements.yml
安裝必要的工具
接下來,我們需要安裝一些必要的工具,包括 pip 和 Docker Python 函式庫。在 provisioning 資料夾中建立 setup.yml:
---
- name: Install Pip.
apt: name=python3-pip state=present
- name: Install Docker Python library.
pip: name=docker state=present
Ansible 需要 Docker Python 函式庫來透過 Python 控制 Docker。
建構和啟動 Docker 容器
這是 Playbook 的核心部分:在 provisioning 資料夾中建立 docker.yml:
---
- name: Build Docker images from Dockerfiles.
docker_image:
name: "{{ item.name }}"
tag: "{{ item.tag }}"
source: build
build:
path: "{{ build_root }}/{{ item.directory }}"
pull: false
state: present
with_items:
- { name: data, tag: latest, directory: data }
- { name: flask, tag: latest, directory: www }
- { name: db, tag: latest, directory: db }
內容解密:
- Docker images 架構:我們使用
docker_image模組來從 Dockerfiles 建構映像檔。每個映像檔都有其名稱、標籤和來源路徑。 - 路徑設定:由於我們是在虛擬機器內建構映像檔,所以使用了
/vagrant/provisioning/[directory]作為路徑。 - 標籤的作用:標籤類別似於 Git 的標籤,允許未來的 Docker 命令使用這些特定版本的映像檔。
啟動容器
- name: Run a Data container.
docker_container:
image: data:latest
name: data
state: present
- name: Run a Flask container.
docker_container:
image: www:latest
name: www
state: started
command: python /opt/www/index.py
ports: "80:80"
- name: Run a MySQL container.
docker_container:
image: db:latest
name: db
state: started
volumes_from: data
ports: "3306:3306"
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: flask
MYSQL_USER: flask
MYSQL_PASSWORD: flask
內容解密:
- 資料容器:確保資料容器存在即可。
- Flask 應用程式容器:確保應用程式不僅執行,還能持續執行。這裡我們提供了一個明確的命令來執行應用程式。
呼叫指令碼會使應用程式在前景執行並將所有日誌記錄到 stdout。
command: python /opt/www/index.py - MySQL 資料函式庫容器:設定 MySQL 資料函式庫的基本組態,包括環境變數和連線埠。
內容解密:
- Flask 應用程式與 MySQL 資料函式庫之間有直接連線。
- Data Container 用來持久化 MySQL 資料函式庫中的資料。
- 每個服務都在其各自的 Docker 容器中執行。
透過這些步驟,玄貓成功地建立了一個完整且可擴充套件的 Flask 應用程式與 MySQL 資料函式庫的 Docker 環境。這樣的架構不僅能夠在不同機器上進行測試,還能確保資料的一致性和永續性。