Ansible git Module: Clone Pull Git Repository อัตโนมัติ

Ansible git module ใช้ clone และ pull repository จาก Git server ไปยัง remote server โดยตรงใน Playbook โดยไม่ต้องรัน command: git clone ตรง ๆ ซึ่งไม่ idempotent และต้องจัดการ error handling เอง

บทความนี้ครอบคลุม parameters หลัก, การ clone และ update repository, การกำหนด branch/tag/commit, การใช้ SSH key สำหรับ private repository, การ deploy code ด้วย symlink pattern และ pattern สำหรับ automated deployment workflow

git Module พื้นฐาน

git module ต้องการ parameter repo (URL ของ repository) และ dest (path ที่จะ clone ไป) เป็นอย่างน้อย ถ้า directory ปลายทางมี repository อยู่แล้ว module จะ pull แทน clone อัตโนมัติ

---
- name: Basic git module usage
  hosts: all
  become: true
  tasks:
    # clone public repository
    - name: Clone application repository
      git:
        repo: https://github.com/example/myapp.git
        dest: /opt/myapp
        version: main

    # update ถ้ามีอยู่แล้ว, clone ถ้ายังไม่มี
    - name: Clone or update repository
      git:
        repo: https://github.com/example/config.git
        dest: /etc/myapp/config
        version: production
        update: true    # default คือ true

    # ไม่ update ถ้ามีอยู่แล้ว (idempotent แบบ strict)
    - name: Clone once, never update
      git:
        repo: https://github.com/example/tools.git
        dest: /opt/tools
        update: false

version — Branch, Tag และ Commit

parameter version รับค่าได้หลายรูปแบบ ทั้ง branch name, tag name และ commit hash ทำให้ควบคุม codebase ที่ deploy ได้อย่างแม่นยำ

---
- name: version parameter examples
  hosts: all
  become: true
  tasks:
    # deploy จาก branch ชื่อ release
    - name: Clone release branch
      git:
        repo: https://github.com/example/myapp.git
        dest: /opt/myapp
        version: release/v2

    # deploy จาก tag (fixed version)
    - name: Clone specific tag
      git:
        repo: https://github.com/example/myapp.git
        dest: /opt/myapp/releases/v2.5.0
        version: v2.5.0

    # deploy จาก commit hash ตายตัว (reproducible build)
    - name: Clone specific commit
      git:
        repo: https://github.com/example/myapp.git
        dest: /opt/myapp/releases/stable
        version: a1b2c3d4e5f6789012345678901234567890abcd

    # ใช้ variable กำหนด version (จาก extra vars หรือ vars file)
    - name: Deploy specified version
      git:
        repo: "{{ app_repo_url }}"
        dest: "/opt/{{ app_name }}/releases/{{ app_version }}"
        version: "{{ app_version }}"

Private Repository — SSH Key Authentication

สำหรับ private repository ที่ต้องการ authentication ใช้ key_file กำหนด SSH private key path บน remote server หรือใช้ accept_hostkey เพื่อ auto-accept host key ครั้งแรก

---
- name: Private repository access
  hosts: all
  become: true
  vars:
    deploy_key_path: /home/deploy/.ssh/deploy_key

  tasks:
    # copy deploy key ไปยัง remote server ก่อน
    - name: Copy deploy SSH key
      copy:
        src: files/deploy_key
        dest: "{{ deploy_key_path }}"
        owner: deploy
        group: deploy
        mode: '0600'

    # clone private repo ด้วย SSH key
    - name: Clone private repository
      git:
        repo: [email protected]:example/private-app.git
        dest: /opt/private-app
        version: main
        key_file: "{{ deploy_key_path }}"
        accept_hostkey: true
      become_user: deploy

    # ใช้ HTTPS พร้อม token (เก็บใน Vault)
    - name: Clone with HTTPS token
      git:
        repo: "https://{{ github_token }}@github.com/example/private-app.git"
        dest: /opt/private-app
        version: main

force และ depth

force: true ใช้เมื่อต้องการล้าง local changes ก่อน pull และ depth ใช้ทำ shallow clone เพื่อประหยัด bandwidth และ disk space บน server

---
- name: force and depth options
  hosts: all
  become: true
  tasks:
    # force: ล้าง local changes แล้ว pull ใหม่
    - name: Force update repository
      git:
        repo: https://github.com/example/myapp.git
        dest: /opt/myapp
        version: main
        force: true    # git reset --hard แล้ว pull

    # shallow clone — ดึงเฉพาะ history ล่าสุด N commits
    - name: Shallow clone (faster for CI)
      git:
        repo: https://github.com/example/myapp.git
        dest: /opt/myapp
        version: main
        depth: 1       # ดึงแค่ commit ล่าสุด

    # ไม่ clone submodules (default)
    - name: Clone with submodules
      git:
        repo: https://github.com/example/myapp.git
        dest: /opt/myapp
        version: main
        recursive: true    # clone submodules ด้วย

register — ตรวจสอบว่า Repository เปลี่ยนแปลงหรือไม่

ใช้ register กับ git module เพื่อตรวจว่ามีการเปลี่ยนแปลงจริงหรือไม่ แล้วนำผลไปใช้ใน conditional tasks เช่น trigger restart เฉพาะเมื่อ code เปลี่ยน

---
- name: Register git changes
  hosts: all
  become: true
  tasks:
    - name: Pull latest code
      git:
        repo: https://github.com/example/myapp.git
        dest: /opt/myapp
        version: main
      register: git_result

    - name: Show git result
      debug:
        msg:
          - "Changed: {{ git_result.changed }}"
          - "Before: {{ git_result.before }}"
          - "After: {{ git_result.after }}"

    # รัน build เฉพาะเมื่อ code เปลี่ยน
    - name: Run build script
      command: /opt/myapp/scripts/build.sh
      when: git_result.changed

    # restart service เฉพาะเมื่อ code เปลี่ยน
    - name: Restart application
      service:
        name: myapp
        state: restarted
      when: git_result.changed

Pattern: Deploy Code ด้วย Release Directory และ Symlink

Pattern มาตรฐานสำหรับ zero-downtime deployment คือ clone ลงใน release directory ที่มี timestamp แล้วสลับ symlink current ไปชี้ release ใหม่ ทำให้ rollback ง่ายโดยแค่ย้าย symlink กลับ

---
- name: Deploy with release directory pattern
  hosts: appservers
  become: true
  vars:
    app_name: myapp
    app_repo: https://github.com/example/myapp.git
    app_version: "{{ deploy_version | default('main') }}"
    release_dir: "/opt/{{ app_name }}/releases/{{ ansible_date_time.epoch }}"
    current_link: "/opt/{{ app_name }}/current"
    shared_dir: "/opt/{{ app_name }}/shared"

  tasks:
    # สร้าง release directory
    - name: Create release directory
      file:
        path: "{{ release_dir }}"
        state: directory
        owner: deploy
        group: deploy
        mode: '0755'

    # clone code ลงใน release directory
    - name: Clone application code
      git:
        repo: "{{ app_repo }}"
        dest: "{{ release_dir }}"
        version: "{{ app_version }}"
        depth: 1
        force: true
      become_user: deploy
      register: git_result

    # link shared directories (logs, uploads, config)
    - name: Link shared directories
      file:
        src: "{{ shared_dir }}/{{ item }}"
        dest: "{{ release_dir }}/{{ item }}"
        state: link
        force: true
      loop:
        - logs
        - uploads
        - config/database.yml

    # รัน migrations ถ้า code เปลี่ยน
    - name: Run database migrations
      command: /opt/myapp/bin/migrate
      args:
        chdir: "{{ release_dir }}"
      when: git_result.changed
      become_user: deploy

    # สลับ symlink ไปชี้ release ใหม่
    - name: Switch current to new release
      file:
        src: "{{ release_dir }}"
        dest: "{{ current_link }}"
        state: link
        force: true

    # restart application
    - name: Restart application service
      service:
        name: "{{ app_name }}"
        state: restarted

สรุป

git module เป็นทางที่ถูกต้องในการ clone และ update repository ด้วย Ansible รองรับ branch, tag และ commit hash ผ่าน version, ใช้ SSH key สำหรับ private repository ผ่าน key_file และตรวจสอบการเปลี่ยนแปลงได้ผ่าน register

Pattern ที่ควรจำ: ใช้ depth: 1 เพื่อประหยัด bandwidth บน production server, ใช้ register เพื่อ trigger downstream tasks เฉพาะเมื่อ code จริง ๆ เปลี่ยน, ใช้ release directory + symlink pattern สำหรับ zero-downtime deployment และเก็บ SSH key หรือ token ใน Ansible Vault เสมอ