相信各位在寫程式都知道我們應該要把重複使用的程式碼抽成 function 讓其他地方可以共用,那 Ansible 的世界裡也是一樣,我們需要透過 Roles 來定義每個不同的小任務。
其實大家可以換個角度想,當我們把任務拆的越小像是一塊塊積木,那是不是我們在將其組合起來是非常方便且更具彈性。
以下結構是筆者多方吸收消化後得出的結論,供大家參考。
roles/
deploy_fastapi/
defaults/ # 使用者可覆蓋的預設變數
main.yml
handlers/ # 服務重載/重啟事件
main.yml
tasks/ # task 進入點
main.yml
templates/ # 放置 Jinja2 templates
fastapi.service.j2
nginx_fastapi.conf.j2
meta/ # metadata (optional)
main.yml
💡Tips:筆者實務上常用 main.yaml 或者 main.yml 為檔案名稱。
---
# fastapi-site.yaml
- name: Use fastapi_app role
hosts: all
become: yes
roles:
- role: fastapi_app
vars:
app_port: 8000
enable_nginx: true
💡Tips:筆者實務上常用 xxxx-site.yaml,會用 site 來表明這個 Playbook。
---
# roles/deploy_fastapi/defaults/main.yaml
app_user: fastapi
app_dir: /opt/fastapi-app
app_port: 8000
enable_nginx: true
---
# roles/deploy_fastapi/handlers/main.yaml
- name: Reload systemd
systemd:
daemon_reload: yes
- name: Reload nginx
service:
name: nginx
state: reloaded
---
# roles/deploy_fastapi/tasks/main.yml
- name: Install base packages
package:
name: "{{ (ansible_facts.os_family == 'RedHat') | ternary(['python3','python3-virtualenv','python3-pip'], ['python3','python3-venv','python3-pip']) }}"
state: present
- name: Ensure app user and directory
block:
- user:
name: "{{ app_user }}"
- file:
path: "{{ app_dir }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: '0755'
- name: Create virtualenv
command: python3 -m venv {{ app_dir }}/venv
args:
creates: "{{ app_dir }}/venv/bin/python"
- name: Install FastAPI and Uvicorn
pip:
name:
- fastapi
- "uvicorn[standard]"
virtualenv: "{{ app_dir }}/venv"
- name: Create sample FastAPI app
copy:
dest: "{{ app_dir }}/main.py"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: '0644'
content: |
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"hello": "world"}
- name: Install nginx (optional)
package:
name: nginx
state: present
when: enable_nginx
- name: Deploy systemd service
template:
src: fastapi.service.j2
dest: /etc/systemd/system/fastapi.service
notify: Reload systemd
- name: Deploy nginx reverse proxy (optional)
template:
src: nginx_fastapi.conf.j2
dest: /etc/nginx/conf.d/fastapi.conf
mode: '0644'
validate: 'nginx -t -c %s'
when: enable_nginx
notify: Reload nginx
- name: Enable and start services
service:
name: "{{ 'nginx' if enable_nginx else 'fastapi' }}"
state: started
enabled: yes
- name: Ensure FastAPI service is running
service:
name: fastapi
state: started
enabled: yes
# roles/fastapi_app/templates/fastapi.service.j2
# Managed by Ansible – DO NOT EDIT
[Unit]
Description=FastAPI app
After=network.target
[Service]
User={{ app_user }}
WorkingDirectory={{ app_dir }}
ExecStart={{ app_dir }}/venv/bin/uvicorn main:app --host 127.0.0.1 --port {{ app_port }}
Restart=always
Environment=PATH={{ app_dir }}/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
[Install]
WantedBy=multi-user.target
# roles/fastapi_app/templates/nginx_fastapi.conf.j2
# Managed by Ansible – DO NOT EDIT
server {
listen 80;
server_name {{ inventory_hostname | default('_') }};
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
筆直建議使用 defaults/main.yml
來定義這個 role 的變數,相信大部分的情境都是夠用的,除非今天各位的變數是 global,那就直接放在 group_vars
裡面即可,這樣其他 role 或者 Playbook 比較容易使用。
明天來看看比較進階一點的 Inventory 管理技巧吧!