AnsibleをGitHub Actionsに組み込む設計入門|SSH SecretsとWorkflowでPush→自動デプロイを実現する方法

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Ansible > AnsibleをGitHub Actionsに組み込む設計入門|SSH SecretsとWorkflowでPush→自動デプロイを実現する方法
「AnsibleのPlaybookをGitで管理しているのに、本番への反映は毎回手元から手動実行している」
「コードレビューを通ったPlaybookがどの環境に入っているのかが、いつも把握できていない」

Ansibleでサーバー構成を自動化した後に訪れるのが、この「実行管理」の課題です。どれほど精密なPlaybookを書いても、実行タイミングや担当者の手作業に依存していると、環境のズレ・適用漏れ・「どのバージョンが本番に入っているか分からない」問題が繰り返し発生します。

この記事では、AnsibleをGitHub Actionsのワークフローに組み込んで「gitのPushを契機にLinuxサーバーへ自動デプロイする」仕組みを解説します。SSHキーのSecrets管理・known_hostsの安全な設定・ワークフローファイルの設計から、ansible-lintによる静的解析・--check --diffによる変更の事前確認・ステージング/本番の環境別デプロイ設計まで、実際のコードで説明します。

実行環境: Rocky Linux 9.4(管理対象ノード)、Ansible 2.16、GitHub Actions(ubuntu-24.04)

この記事のポイント

・GitHubリポジトリへのPushを契機にAnsibleを自動実行できる
・SSHキーはBase64エンコードしてGitHub Secretsで安全に管理する
・lint→check(--check --diff)→deployの3段階で本番適用前に変更を確認できる
・GitHub Environmentsでstaging→production承認フローを実現できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

手動実行が抱える限界と自動化の必要性

Ansibleは「コードとして管理できる構成管理ツール」ですが、実行タイミングを人手に委ねていると次の問題が発生します。

・Gitにマージされた変更が本番に反映されないまま放置される
・誰がいつどの環境に対して実行したのかがログに残らない
・「実行忘れ」による本番/ステージング環境のズレが蓄積する
・複数のPlaybookを順序どおりに実行する手順が属人管理になる
・Playbookに構文エラーが混入していても、手動実行するまで誰も気づかない

GitHub ActionsにAnsibleの実行を委ねることで、これらをまとめて解消できます。Pushのたびに自動実行・ログ記録・成否通知まで一貫して管理できる状態が作れます。さらに、lint(静的解析)とdry-run(--check --diff)を組み合わせると、本番に適用する前に「Playbookに問題がないか」「実際に何が変わるか」を自動で確認するフローが完成します。

「Playbookを書くことがゴールではなく、それが確実に適用されることがゴール」という観点で、CI/CDへの組み込みはAnsible活用の自然な次のステップです。

GitHub ActionsとAnsibleの連携設計(全体像)

GitHub ActionsからAnsibleを実行する構成は、大きく2つのパターンがあります。

1. パターンA:GitHub-hostedランナー

GitHubが提供するクラウド上のランナー(ubuntu-24.04等)にAnsibleをインストールし、外部のLinuxサーバーへSSH越しにPlaybookを実行します。

GitHub Push │ ▼ GitHub-hostedランナー(ubuntu-24.04) │ pip install ansible-core ansible-lint │ SSH鍵をSecretsから展開 ▼ SSH(port 22) │ ▼ 管理対象ノード(Rocky Linux 9.4) Playbookによる構成変更

・メリット: 管理インフラの追加が不要。ランナーのメンテナンス不要。
・デメリット: SSH接続元がGitHubのIP帯になる。ファイアウォールをGitHub MetaのIPレンジに開放する必要がある。

2. パターンB:self-hostedランナー

自社のコントロールノード(Ansibleをインストール済みのLinuxサーバー)にGitHub Actionsランナーを設置します。

GitHub Push │ ▼ 自社コントロールノード(GitHub Actionsランナー兼Ansibleコントロール) │ 既存のAnsible環境をそのまま使用 ▼ SSH(内部ネットワーク) │ ▼ 管理対象ノード(Rocky Linux 9.4)

・メリット: SSH接続元が固定IP(自社サーバー)になる。ファイアウォール制御が容易。
・デメリット: ランナー自体の管理が追加で発生する。

プライベートクラウド・プライベートVPC環境ではパターンBが現実的です。管理対象ノードがパブリッククラウド上にあり外部SSH可能ならパターンAがシンプルです。

SSHキーをGitHub Secretsで安全に管理する手順

GitHub ActionsからSSHでAnsible管理対象ノードへ接続するには、SSH秘密鍵をワークフローに安全に渡す必要があります。

1. Ansible専用のSSHキーペアを生成する

管理対象ノードへの接続に使うSSHキーは、個人鍵と分けて「Ansible CI/CD専用」で生成することを強く推奨します。鍵を分けることで、ローテーション・無効化の影響範囲を限定できます。

# Ansible GitHub Actions用のSSHキーペアを生成(パスフレーズなし・ed25519推奨) $ ssh-keygen -t ed25519 -C "ansible-github-actions" -f ~/.ssh/id_ansible_gha -N "" Generating public/private ed25519 key pair. Your identification has been saved in /home/ops/.ssh/id_ansible_gha Your public key has been saved in /home/ops/.ssh/id_ansible_gha.pub The key fingerprint is: SHA256:xE7T9mKrD2qNpLb3cFvH8sWo6iYjAeZu1XnR4gTd0kM ansible-github-actions

生成した公開鍵を管理対象ノードへ配布します。

# 管理対象ノードへ公開鍵を配布 $ ssh-copy-id -i ~/.ssh/id_ansible_gha.pub ansible-user@192.0.2.10 /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/ops/.ssh/id_ansible_gha.pub" /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s) Number of key(s) added: 1

2. 秘密鍵をBase64エンコードしてGitHub Secretsに登録する

秘密鍵をそのままGitHub Secretsに貼り付けると、改行が失われて正しく展開できない場合があります。Base64エンコードして1行にまとめるのが確実な方法です。

# 秘密鍵をBase64エンコード(改行なしで出力) $ base64 -w 0 ~/.ssh/id_ansible_gha LS0tLS1CRUdJTiBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0KYjNCbGJuTnphQzFyWlhrdGRqRUFBQUFBQ...(省略)

出力文字列をコピーして、GitHubリポジトリの `Settings > Secrets and variables > Actions` から `ANSIBLE_SSH_KEY` として登録します。同様に管理対象ホストのIPアドレスを `TARGET_HOST` として登録しておきます。

Playbookを事前検証する(ansible-lintと--check --diff)

本番への適用前に「Playbookに構文エラーや非推奨の書き方がないか」「実際に何が変わるか」を自動確認するステップを追加すると、デプロイの安全性が大幅に上がります。

・ansible-lint:Playbookの静的解析ツール。推奨されない書き方・非推奨モジュールの使用・YAML構文エラーを実行前に検出する
・--check --diff:管理対象ノードに実際に接続してPlaybookをドライラン(試し走り)する。変更は加えず「何がどう変わるか」をdiff形式で表示する

この2つをデプロイより前のステップとして配置することで、lint→check(dry-run)→deployの3段階検証フローが完成します。

1. ansible-lintのインストールと実行

# ansible-lintのインストール(ansible-coreと同時に入れる) $ pip install --quiet "ansible-core==2.16.*" ansible-lint # Playbookに対してlintを実行 $ ansible-lint site.yml Passed: 0 violation(s) found

違反があれば次のように出力されます。Jobが失敗扱いになり、後続のデプロイステップは実行されません。

$ ansible-lint site.yml WARNING Listing 1 violation(s) that are fatal yaml[truthy]: Use "true" or "false" instead of "yes" or "no" roles/nginx/tasks/main.yml:12 Finished with 1 failure(s), 0 warning(s) on 5 files.

2. --check --diffによるドライランの出力例

lintが通った後、`--check --diff` で管理対象ノードに接続して差分を確認します。ファイルの変更内容がdiff形式で表示されるため、本番適用の判断材料になります。

$ ansible-playbook site.yml \ -i inventory/staging.ini \ -u ansible-user \ --private-key ~/.ssh/id_ed25519 \ --check --diff PLAY [webservers] **************************************************** TASK [nginx : Deploy nginx config] *********************************** --- before: /etc/nginx/nginx.conf +++ after: /home/runner/work/repo/roles/nginx/templates/nginx.conf.j2 @@ -10,7 +10,7 @@ - worker_processes 2; + worker_processes 4; changed: [192.0.2.10] PLAY RECAP *********************************************************** 192.0.2.10 : ok=2 changed=1 unreachable=0 failed=0

diffの出力でworker_processesが2→4に変わることが事前に確認できます。この内容に問題がなければ、次のdeployステップで本番適用が走ります。

GitHub Actionsワークフローファイルの設計と書き方

リポジトリのルートに `.github/workflows/deploy.yml` を作成します。lint・check(dry-run)・deployの3ステップを含む構成です。

name: Ansible Deploy on: push: branches: - main jobs: deploy: runs-on: ubuntu-24.04 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Ansible run: | pip install --quiet "ansible-core==2.16.*" ansible-lint - name: Setup SSH key env: SSH_PRIVATE_KEY: ${{ secrets.ANSIBLE_SSH_KEY }} run: | mkdir -p ~/.ssh chmod 700 ~/.ssh echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 - name: Setup known_hosts run: | ssh-keyscan -H "${{ secrets.TARGET_HOST }}" >> ~/.ssh/known_hosts - name: Lint Playbook run: | ansible-lint site.yml - name: Check (dry-run) run: | ansible-playbook \ -i inventory/production.ini \ -u ansible-user \ --private-key ~/.ssh/id_ed25519 \ --check --diff \ site.yml - name: Run Ansible Playbook run: | ansible-playbook \ -i inventory/production.ini \ -u ansible-user \ --private-key ~/.ssh/id_ed25519 \ site.yml - name: Cleanup SSH key if: always() run: rm -f ~/.ssh/id_ed25519

`if: always()` を付けた `Cleanup SSH key` ステップが重要です。前のステップが失敗しても必ず実行され、ランナー上に秘密鍵が残ることを防ぎます。

`Run Ansible Playbook` ステップが正常に完了すると次のような出力が得られます。

PLAY [webservers] ************************************************************** TASK [Gathering Facts] ********************************************************* ok: [192.0.2.10] TASK [nginx : Install nginx] *************************************************** ok: [192.0.2.10] TASK [nginx : Deploy nginx config] ********************************************* changed: [192.0.2.10] RUNNING HANDLER [nginx : restart nginx] **************************************** changed: [192.0.2.10] PLAY RECAP ********************************************************************* 192.0.2.10 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

`failed=0` であればデプロイ成功です。`ansible-playbook` がゼロ以外の終了コードを返した場合、GitHub Actionsはワークフローを自動で失敗扱いにして通知を出します。

GitHub-hostedランナーとself-hostedランナーの使い分け

どちらを選ぶかは以下の基準で判断します。
判断軸 GitHub-hosted self-hosted
管理対象ノードの場所 パブリッククラウド・外部SSH可能 オンプレミス・プライベートVPC
SSH接続元IP GitHubのIPレンジ(変動あり) 固定IP(自社サーバー)
ランナー管理コスト 不要 自社で管理が必要
Ansible環境 毎回pip installで用意 既存環境をそのまま利用
self-hostedランナーをコントロールノードに設置する場合、GitHub UIの `Settings > Actions > Runners > New self-hosted runner` の手順に従ってインストールします。

# Rocky Linux 9.4 上でのself-hostedランナーセットアップ例 $ mkdir actions-runner && cd actions-runner $ curl -o actions-runner-linux-x64-2.319.1.tar.gz -L \ https://github.com/actions/runner/releases/download/v2.319.1/actions-runner-linux-x64-2.319.1.tar.gz $ tar xzf ./actions-runner-linux-x64-2.319.1.tar.gz $ ./config.sh --url https://github.com/ORG/REPO --token AXXXXXXX $ sudo ./svc.sh install $ sudo ./svc.sh start # サービス状態を確認 $ sudo ./svc.sh status * actions.runner.ORG-REPO.hostname.service - GitHub Actions Runner (ORG-REPO.hostname) Loaded: loaded (/etc/systemd/system/actions.runner.ORG-REPO.hostname.service; enabled) Active: active (running)

self-hostedランナーを使う場合は、ワークフローの `runs-on` を次のように変えるだけです。

jobs: deploy: runs-on: self-hosted

Ansible環境を既存のコントロールノードで使い回せるため、GitHub-hostedランナーと違ってPlaybookの実行に必要な依存関係(カスタムコレクション・ロール等)をインストール済みの状態で使えるメリットがあります。インフラのPlaybook設計からGitHub ActionsによるCI/CD統合まで体系的に学べるAnsible実践ハンズオン(LinuxMaster)では、ここで解説した内容をハンズオンで実践できます。

ステージング・本番を分けた環境別デプロイ設計

mainブランチへのPushでステージングに自動デプロイし、本番への反映はタグ付きリリース+手動承認を必要とする設計が現場でよく使われます。

1. Inventoryのディレクトリ構成

環境ごとにInventoryを分けて管理します。

inventory/ staging/ hosts.ini # staging環境のホスト定義 group_vars/ all.yml # staging用変数(接続先・設定値) production/ hosts.ini # production環境のホスト定義 group_vars/ all.yml # production用変数(接続先・設定値)

ワークフローからはInventoryのパスを切り替えるだけで、同じPlaybookをStaging/Productionで使い回せます。

2. ブランチとタグで環境を分けたワークフロー

name: Ansible Deploy on: push: branches: - main # ステージングへ自動デプロイ tags: - 'v*' # 本番へのデプロイ(タグ付きリリース) jobs: deploy-staging: if: github.ref_type == 'branch' runs-on: ubuntu-24.04 environment: staging steps: - uses: actions/checkout@v4 - name: Setup SSH key env: SSH_PRIVATE_KEY: ${{ secrets.ANSIBLE_SSH_KEY }} run: | mkdir -p ~/.ssh && chmod 700 ~/.ssh echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H "${{ secrets.STAGING_HOST }}" >> ~/.ssh/known_hosts - name: Install Ansible run: pip install --quiet "ansible-core==2.16.*" ansible-lint - name: Lint Playbook run: ansible-lint site.yml - name: Check (dry-run) run: | ansible-playbook -i inventory/staging -u ansible-user \ --private-key ~/.ssh/id_ed25519 --check --diff site.yml - name: Deploy to staging run: | ansible-playbook -i inventory/staging -u ansible-user \ --private-key ~/.ssh/id_ed25519 site.yml - name: Cleanup SSH key if: always() run: rm -f ~/.ssh/id_ed25519 deploy-production: if: github.ref_type == 'tag' runs-on: ubuntu-24.04 environment: production # GitHub Environmentsで手動承認を設定 steps: - uses: actions/checkout@v4 - name: Setup SSH key env: SSH_PRIVATE_KEY: ${{ secrets.ANSIBLE_SSH_KEY }} run: | mkdir -p ~/.ssh && chmod 700 ~/.ssh echo "$SSH_PRIVATE_KEY" | base64 -d > ~/.ssh/id_ed25519 chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H "${{ secrets.PRODUCTION_HOST }}" >> ~/.ssh/known_hosts - name: Install Ansible run: pip install --quiet "ansible-core==2.16.*" ansible-lint - name: Lint Playbook run: ansible-lint site.yml - name: Check (dry-run) run: | ansible-playbook -i inventory/production -u ansible-user \ --private-key ~/.ssh/id_ed25519 --check --diff site.yml - name: Deploy to production run: | ansible-playbook -i inventory/production -u ansible-user \ --private-key ~/.ssh/id_ed25519 site.yml - name: Cleanup SSH key if: always() run: rm -f ~/.ssh/id_ed25519

GitHub Environments(リポジトリの `Settings > Environments`)で `production` 環境に `Required reviewers` を設定すると、タグPushに対してレビュアーの承認が下りるまで本番デプロイは実行されません。コードの変更から本番適用までに「人の目」を挟む承認ゲートが、ワークフロー設定だけで実現できます。

よくあるエラーと対処法

【エラー1】Host key verification failed.

known_hostsにホストが登録されていない場合に発生します。`ssh-keyscan` のステップがSSH接続より前に配置されているか確認します。

UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Host key verification failed.", "unreachable": true}

対処: `ssh-keyscan` を必ずSSH接続より前のステップに配置すること。本番運用ではknown_hostsファイルをリポジトリに含めて管理し、動的スキャンを避ける設計も推奨されます。

【エラー2】Permission denied (publickey)

秘密鍵のデコードに失敗しているか、公開鍵が管理対象ノードの `~/.ssh/authorized_keys` に入っていない場合に発生します。

# ローカルでBase64デコードを検証(先頭行の確認) $ cat ~/.ssh/id_ansible_gha | base64 -w 0 | base64 -d | head -1 -----BEGIN OPENSSH PRIVATE KEY-----

先頭行が `-----BEGIN OPENSSH PRIVATE KEY-----` であれば鍵の形式は正常です。GitHub Secretsに登録した値の末尾に余分な改行が入っていないかも確認します。

【エラー3】Could not connect to target host

ファイアウォールでSSH(port 22)が遮断されている場合に発生します。パターンA(GitHub-hostedランナー)の場合、GitHub MetaのIPレンジ(`curl https://api.github.com/meta` で取得可能)を許可リストに追加します。パターンB(self-hostedランナー)への切り替えも有効な対処です。

【エラー4】ansible-lintでE403/E501等の違反が出る

チームで共有するリポジトリで既存のPlaybookに一括lintをかけると、大量の違反が検出されることがあります。まず `.ansible-lint` 設定ファイルで段階的に対象ルールを絞る方法が現実的です。

# .ansible-lint(プロジェクトルートに置く) warn_list: - yaml[truthy] # 警告止まり(エラー扱いしない) skip_list: - role-name # 既存ロール名のルールをスキップ

本記事のまとめ

やりたいこと 設計ポイント
GitHub Pushで自動デプロイ on.push.branches でブランチを指定する
SSH秘密鍵の安全な受け渡し base64エンコードしてGitHub Secretsに登録する
known_hostsの事前設定 ssh-keyscan をSSH接続より前のステップで実行する
Playbookの静的解析 ansible-lintをlintステップとして配置する
本番適用前の変更確認 --check --diffでdiff出力を確認してからdeployを実行する
SSH鍵の後処理 if: always() ステップでid_ed25519を削除する
ステージング/本番の分離 branchとtagでjobを分け、GitHub Environmentsで制御する
外部アクセス不要の構成 self-hostedランナーをコントロールノードに設置する
本番デプロイの承認制御 GitHub EnvironmentsのRequired reviewers機能を使う
AnsibleをGitHub Actionsに組み込むと、Playbookの変更が確実に適用される「仕組みとしての構成管理」が実現します。lint→check→deployの3段階検証フローを加えることで、手動実行の属人性を排除するだけでなく、本番への誤適用リスクも体系的に下げられます。ログ・通知・承認フローを一元管理することで、チームの規模が変わっても安定した運用が続けられます。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
Ansible実践ハンズオンの詳細を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。