GitHub Actions

GitHub Actions #

GitHub Actions adalah platform CI/CD yang terintegrasi langsung dengan repository GitHub. Untuk tim yang sudah menggunakan GitHub, integrasi ini sangat alami — workflow didefinisikan sebagai file YAML di direktori .github/workflows/, dipicu oleh event Git (push, pull_request, merge), dan berjalan di runner yang disediakan GitHub atau runner self-hosted milik sendiri. Artikel ini membahas pola integrasi Ansible dengan GitHub Actions yang komprehensif dan aman.

Workflow Dasar: Lint dan Syntax Check #

Setiap pull request harus melewati validasi sebelum bisa di-merge:

# .github/workflows/validate.yml
name: Validate Ansible

on:
  pull_request:
    paths:
      - '**.yml'
      - '**.yaml'
      - '**.j2'
      - 'roles/**'
      - 'playbooks/**'
      - 'inventory/**'

jobs:
  lint:
    name: Ansible Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: pip-${{ hashFiles('requirements.txt', 'requirements-dev.txt') }}
          restore-keys: pip-

      - name: Install Ansible dan lint
        run: |
          pip install ansible ansible-lint
          ansible-galaxy install -r requirements.yml          

      - name: Syntax check semua playbook
        run: |
          for playbook in playbooks/*.yml; do
            echo "Checking: $playbook"
            ansible-playbook "$playbook" --syntax-check \
              -i inventory/staging/ \
              -e @tests/test-vars.yml
          done          

      - name: Jalankan ansible-lint
        run: ansible-lint --profile production

Molecule Test di GitHub Actions #

# .github/workflows/molecule.yml
name: Molecule Test

on:
  pull_request:
    paths:
      - 'roles/**'

jobs:
  molecule:
    name: Test Role — ${{ matrix.role }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        role:
          - common
          - nginx
          - postgresql
          - docker
      fail-fast: false    # Lanjutkan test role lain meski satu gagal

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Cache pip
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: molecule-${{ matrix.role }}-${{ hashFiles('**/requirements*.txt') }}

      - name: Install test dependencies
        run: |
          pip install ansible molecule molecule-plugins[docker] pytest-testinfra
          ansible-galaxy install -r requirements.yml          

      - name: Jalankan Molecule test
        run: |
          cd roles/${{ matrix.role }}
          molecule test          
        env:
          PY_COLORS: '1'
          ANSIBLE_FORCE_COLOR: '1'
          MOLECULE_DISTRO: ubuntu2204

      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: molecule-logs-${{ matrix.role }}
          path: roles/${{ matrix.role }}/molecule/default/*.log

Deployment Multi-Environment dengan Approval #

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]
    tags: ['v*.*.*']

jobs:
  build:
    name: Build dan Push Image
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ steps.tag.outputs.tag }}
    steps:
      - uses: actions/checkout@v4

      - name: Generate tag
        id: tag
        run: echo "tag=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT

      - name: Login ke registry
        uses: docker/login-action@v3
        with:
          registry: registry.company.com
          username: ${{ secrets.REGISTRY_USER }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build dan push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: |
            registry.company.com/myapp:${{ steps.tag.outputs.tag }}
            registry.company.com/myapp:latest            

  deploy-staging:
    name: Deploy ke Staging
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - name: Setup Ansible
        run: pip install ansible && ansible-galaxy install -r requirements.yml

      - name: Setup credentials
        run: |
          install -d -m 700 ~/.ssh
          echo "${{ secrets.STAGING_SSH_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          echo "${{ secrets.VAULT_PASS_STAGING }}" > /tmp/.vault_pass
          chmod 600 /tmp/.vault_pass          

      - name: Deploy ke staging
        run: |
          ansible-playbook -i inventory/staging/ playbooks/deploy.yml \
            -e "app_version=${{ needs.build.outputs.image_tag }}" \
            --vault-password-file /tmp/.vault_pass \
            --private-key ~/.ssh/deploy_key          

      - name: Smoke test staging
        run: |
          sleep 15
          curl -f https://staging.company.com/health
          curl -f https://staging.company.com/api/version          

      - name: Cleanup credentials
        if: always()
        run: rm -f ~/.ssh/deploy_key /tmp/.vault_pass

  deploy-production:
    name: Deploy ke Production
    needs: [build, deploy-staging]
    runs-on: ubuntu-latest
    environment:
      name: production       # Set required reviewers di GitHub Settings → Environments
      url: https://app.company.com
    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v4

      - name: Setup Ansible
        run: pip install ansible && ansible-galaxy install -r requirements.yml

      - name: Setup credentials
        run: |
          install -d -m 700 ~/.ssh
          echo "${{ secrets.PROD_SSH_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          echo "${{ secrets.VAULT_PASS_PROD }}" > /tmp/.vault_pass
          chmod 600 /tmp/.vault_pass          

      - name: Deploy ke production
        run: |
          ansible-playbook -i inventory/production/ playbooks/deploy.yml \
            -e "app_version=${{ needs.build.outputs.image_tag }}" \
            --vault-password-file /tmp/.vault_pass \
            --private-key ~/.ssh/deploy_key          

      - name: Verifikasi production
        run: |
          sleep 30
          curl -f https://app.company.com/health          

      - name: Cleanup
        if: always()
        run: rm -f ~/.ssh/deploy_key /tmp/.vault_pass

Reusable Workflow #

Untuk menghindari duplikasi konfigurasi deployment di banyak repository:

# .github/workflows/reusable-deploy.yml (di shared repository)
name: Reusable Deploy

on:
  workflow_call:           # Bisa dipanggil dari workflow lain
    inputs:
      environment:
        required: true
        type: string
      image_tag:
        required: true
        type: string
      playbook:
        required: false
        type: string
        default: playbooks/deploy.yml
    secrets:
      ssh_key:
        required: true
      vault_password:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        run: |
          pip install ansible && ansible-galaxy install -r requirements.yml
          echo "${{ secrets.ssh_key }}" > /tmp/deploy_key
          echo "${{ secrets.vault_password }}" > /tmp/.vault_pass
          chmod 600 /tmp/deploy_key /tmp/.vault_pass
          ansible-playbook -i inventory/${{ inputs.environment }}/ \
            ${{ inputs.playbook }} \
            -e "app_version=${{ inputs.image_tag }}" \
            --vault-password-file /tmp/.vault_pass \
            --private-key /tmp/deploy_key
          rm -f /tmp/deploy_key /tmp/.vault_pass          
# Di repository aplikasi — panggil reusable workflow
jobs:
  deploy-staging:
    uses: company/shared-workflows/.github/workflows/reusable-deploy.yml@main
    with:
      environment: staging
      image_tag: ${{ needs.build.outputs.tag }}
    secrets:
      ssh_key: ${{ secrets.STAGING_SSH_KEY }}
      vault_password: ${{ secrets.VAULT_PASS_STAGING }}

Self-Hosted Runner untuk Akses Private Network #

Jika managed node ada di jaringan private yang tidak bisa diakses dari internet:

# roles/github-runner/tasks/main.yml
---
- name: Download GitHub Actions runner
  get_url:
    url: "https://github.com/actions/runner/releases/download/v{{ runner_version }}/actions-runner-linux-x64-{{ runner_version }}.tar.gz"
    dest: /home/github-runner/actions-runner.tar.gz

- name: Extract runner
  unarchive:
    src: /home/github-runner/actions-runner.tar.gz
    dest: /home/github-runner/
    remote_src: true

- name: Konfigurasi runner
  command: >
    /home/github-runner/config.sh
    --url https://github.com/{{ github_org }}
    --token {{ vault_runner_token }}
    --name {{ inventory_hostname }}
    --labels self-hosted,production-network
    --unattended    
  become_user: github-runner

- name: Install dan jalankan runner sebagai service
  command: /home/github-runner/svc.sh install
  become: true

Ringkasan #

  • Pisahkan workflow: validate.yml untuk PR, molecule.yml untuk test role, deploy.yml untuk deployment — satu workflow fokus pada satu tujuan.
  • environment dengan required reviewers di GitHub Settings adalah cara termudah untuk memerlukan approval sebelum deploy ke production.
  • Gunakan matrix untuk menjalankan Molecule test semua role secara paralel — jauh lebih cepat dari test satu per satu.
  • needs: + outputs: untuk meneruskan nilai antar job — image tag yang dihasilkan build job harus tersedia di deploy job.
  • Reusable workflow (workflow_call) untuk berbagi konfigurasi deployment antar repository — satu perubahan di shared workflow langsung berlaku untuk semua aplikasi.
  • Self-hosted runner untuk akses ke managed node di private network — runner berjalan di dalam jaringan yang sama dengan server target.

← Sebelumnya: Pipeline Design   Berikutnya: GitLab CI →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact