iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0
DevOps

不爆肝學習 Ansible 的短暫30天系列 第 14

Day14 - ansible.cfg 介紹與 FastAPI 自動化部署實戰

  • 分享至 

  • xImage
  •  

今日目標

  • 補充前面沒講到的內容
  • 使用 Ansible 自動化部署 FastAPI (Uvicorn + systemd)

什麼是 ansible.cfg

寫到今天突然發現筆者竟然沒介紹到 Ansible 最重要的一個東西,那就是 ansible.cfg,這個檔案是負責 Ansible 的主要配置,通常會將其放在專案目錄裡,或者是系統的 etc/ansible/ansible.cfg

# ansible.cfg
[defaults]
# inventory file 的位置
inventory = ./inventories

# role file 的位置
roles_path = ./roles

# Ansible 用 SSH 連線到遠端主機時,會檢查主機的 SSH host key,確保遠端主機沒有被冒充
host_key_checking = False

# SSH 連線超時時間,單位為秒
timeout = 600

# 這個設定筆者蠻建議大家可以打開的,因為可以很清楚看到什麼 task 執行了多少時間
callbacks_enabled = profile_tasks

# 並行執行的數量 (default: 5)
forks = 20

# 設定輸出格式 (預設是 default,可改成 yaml 或 json,yaml 比較容易閱讀)
stdout_callback = yaml

[ssh_connection]
# ControlMaster=auto 允許多個 SSH 連線共用一個控制連線
# ControlPersist=60s 在最後一個連線結束後,仍保留控制連線 60 秒,避免重複建立 SSH
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

# 預設 Ansible 會先將模組傳到遠端再執行,開啟 pipelining 後改為透過單一管道直接執行
pipelining = True

以上內容是筆者比較常用的設定,其餘大家可以參考一下文件

那了解完 ansible.cfg 後我們來試著部署一個 FastAPI

準備一份 host.yaml

# 把剛剛創建好的機器資訊填入,請確保可以 ssh 連線到機器
---
web_servers:
  hosts:
    vultr:
      ansible_host: your_ip
      ansible_port: 22
      ansible_user: your_user_name
      ansible_python_interpreter: /usr/bin/python3

準備兩份 jinja2 template

# 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
# 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;
  }
}

準備一份 Playbook

---
# fastapi-stack.yml
- name: Day14 FastAPI deployment
  # 因為筆者的 host.yaml 有定義成 web_servers,這邊就看各位的定義去做設定即可
  hosts: web_servers
  # 讓我們可以使用 sudo 權限
  become: yes
  # 搜集機器資訊
  gather_facts: true
  vars:
    # 設定變數,像是 port 之類的
    app_user: fastapi
    app_dir: /opt/fastapi-app
    app_port: 8000
    # 若不需反向代理改為 false
    enable_nginx: true

    packages:
      Debian: [python3, python3-venv, python3-pip]
      RedHat: [python3, python3-virtualenv, python3-pip]
    nginx_pkg:
      Debian: nginx
      RedHat: nginx

  # 在執行這份 Playbook 前會做的任務
  pre_tasks:
    - 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'

  tasks:
    - name: Install base packages
      package:
        name: "{{ packages[ansible_facts['os_family']] | default(packages['Debian']) }}"
        state: present

    - 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_pkg[ansible_facts['os_family']] | default(nginx_pkg['Debian']) }}"
        state: present
      when: enable_nginx

    - name: Deploy systemd service for FastAPI
      template:
        src: templates/fastapi.service.j2
        dest: /etc/systemd/system/fastapi.service
        mode: '0644'
      notify: Reload systemd

    - name: Deploy nginx reverse proxy (optional)
      template:
        src: templates/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 started
      service:
        name: fastapi
        state: started
        enabled: yes

  handlers:
    - name: Reload systemd
      systemd:
        daemon_reload: yes

    - name: Reload nginx
      service:
        name: nginx
        state: reloaded

執行任務

# Run Playbook
# 若已在 ansible.cfg 設定 inventory 的話就不需要指定
ansible-playbook fastapi-stack.yml

# 若未設定,或想手動指定
ansible-playbook -i snippets/host.yaml fastapi-stack.yml

作業練習時間

  • 練習 1:把 app_portenable_nginx 放到 group_vars/all.yaml
  • 練習 2:將範例應用改為多路由,並使用模板化的 systemd 參數 (如 EnvironmentFile)
  • 練習 3:在 Nginx 模板加入 gzip 與安全標頭

注意事項 (安全與設定)

  • 請不要在 Playbook/模板設定任何敏感資訊,如果需要請善用 Ansible Vault、SOPSgit-crypt 進行敏感資訊加密

明日預告

明天讓我們來深入了解 Role 的概念吧!


上一篇
Day13 - 善用 Ansible Facts 讓 Playbook 更聰明
系列文
不爆肝學習 Ansible 的短暫30天14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言