AnsibleとJenkinsの統合: CI/CDパイプラインの自動化

AnsibleとJenkinsを統合して、CI/CDパイプラインからプレイブックを実行し、SSH認証情報を管理し、一貫したデプロイを実現します。

AnsibleとJenkinsの統合:CI/CDパイプラインの自動化

AnsibleとJenkinsを統合することで、CI/CDパイプラインがアーティファクトをビルドし、テストを実行し、Jenkins外部で使用するのと同じプレイブックでデプロイできるようになります。主な課題は、パイプラインをシェルコマンドの山に変えずに、認証情報、インベントリ、プレイブックの実行を配線することです。

このガイドでは、実践的なセットアップを紹介します。Jenkinsがパイプラインを調整し、Ansibleがターゲットホストへのデプロイと設定を担当します。例ではSSHベースのLinuxデプロイを想定していますが、同じパターンは環境固有のインベントリやロールでも機能します。

JenkinsとAnsibleの連携方法

パイプラインを書く前に、責任を分離します:

  • Jenkins: コードのチェックアウト、アーティファクトのビルド、テストの実行、ログの収集、デプロイメントステージの実行タイミングを決定します。
  • Ansible: ターゲットホストへの接続、アーティファクトのコピー、設定の書き込み、サービスの管理、デプロイメントステップの冪等性を維持します。

JenkinsとAnsibleを統合する理由

この分割により、チームに明確な境界が生まれます:

  1. JenkinsはリリースフローをJenkinsfileに保存します。
  2. Ansibleはインフラストラクチャアクションをプレイブックとロールに保存します。
  3. インベントリファイルが、ステージング、本番、その他の環境にどのホストが属するかを決定します。
  4. Jenkinsの認証情報がSSHキー、Vaultパスワード、トークンを保護します。

統合の前提条件

始める前に、以下が揃っていることを確認してください:

  • 動作するJenkinsコントローラーと、デプロイメントジョブを実行できる少なくとも1つのエージェント。
  • ansible-playbookを実行するJenkinsエージェントにAnsibleがインストールされていること。
  • そのエージェントからSSH経由で到達可能なターゲットマシン。
  • 自動化専用のSSHキーペア。
  • バージョン管理下にあるアプリケーションコード、インベントリ、プレイブック、ロール。

JenkinsのAnsible対応設定

JenkinsがAnsibleを安全に実行するには、エージェント上のAnsibleランタイムとターゲットホストの認証情報の2つが必要です。

1. Ansibleプラグインのインストール

シェルステップから直接ansible-playbookを呼び出すこともできますが、Jenkins Ansibleプラグインを使用すると、設定と出力の管理が容易になります。

  1. Manage Jenkins > Manage Pluginsに移動します。
  2. Availableタブに移動し、「Ansible」を検索します。
  3. 「Ansible」プラグインを選択し、Install without restartまたはDownload now and install after restartをクリックします。

2. SSH認証情報の設定

Ansibleはターゲットサーバーへの接続にSSHを使用します。秘密鍵はリポジトリではなく、Jenkinsの認証情報に保存します。

  1. Manage Jenkins > Manage Credentialsに移動します。
  2. ジョブで使用する認証情報ストアを選択します。
  3. Add Credentialsをクリックします。
  4. SSH Username with private keyを選択します。
  5. ansible-ssh-keyのような安定したIDを設定します。
  6. ターゲットマシンで使用するSSHユーザー名を入力します。
  7. 秘密鍵を追加し、認証情報を保存します。

ヒント:キー管理のベストプラクティス

  • SSH秘密鍵をJenkinsfile、インベントリ、プレイブックにハードコードしないでください。
  • 個人のキーではなく、専用の自動化キーを使用してください。
  • チームのセキュリティポリシーに合わせたスケジュールでキーをローテーションしてください。

Ansibleを使用したJenkinsパイプラインの設計

デプロイメントロジックがアプリケーションコードと一緒にレビューされるように、Jenkinsfileで宣言的パイプラインを使用します。

基本的なパイプライン構造

以下は、ビルド、ステージングデプロイ、承認付き本番デプロイのコンパクトな例です:

// Jenkinsfile
pipeline {
    agent any

    environment {
        // 必要に応じて環境変数を定義
        ANSIBLE_HOST_KEY_CHECKING = 'False' // 本番環境では注意。known_hostsを推奨
    }

    stages {
        stage('ソースのチェックアウト') {
            steps {
                git 'https://your-scm-url/your-repo.git'
            }
        }

        stage('アプリケーションのビルド') {
            // このステージではJAR、WAR、Dockerイメージなどをビルドする可能性があります
            // 例:Spring Boot JARのビルド
            steps {
                sh 'mvn clean package'
            }
        }

        stage('単体テストの実行') {
            steps {
                sh 'mvn test'
            }
        }

        stage('ステージング環境へのデプロイ') {
            steps {
                script {
                    // SSHエージェントを使用して秘密鍵をAnsibleで利用可能にする
                    sshagent(credentials: ['ansible-ssh-key']) {
                        // Ansibleプレイブックの実行
                        sh 'ansible-playbook -i inventory/staging.ini playbooks/deploy_app.yml \
                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }

        stage('本番環境へのデプロイ') {
            // このステージでは手動承認が必要になる場合があります
            input {
                message "本番環境へのデプロイを続行しますか?"
                ok "本番環境にデプロイ"
            }
            steps {
                script {
                    sshagent(credentials: ['ansible-ssh-key']) {
                        sh 'ansible-playbook -i inventory/production.ini playbooks/deploy_app.yml \
                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }
    }

    post {
        always {
            echo 'パイプラインが終了しました。'
        }
        success {
            echo 'パイプラインが成功しました!'
            // slackSend channel: '#deployments', message: "デプロイ成功: ${env.BUILD_URL}"
        }
        failure {
            echo 'パイプラインが失敗しました!'
            // slackSend channel: '#deployments', message: "デプロイ失敗: ${env.BUILD_URL}"
        }
    }
}

主要なパイプラインの詳細

  • agent anyはデモ用です。本番環境では、Ansibleがインストールされていることを保証するエージェントラベルを使用してください。
  • sshagent(credentials: ['ansible-ssh-key'])は、そのブロック内でのみ秘密鍵を公開します。
  • ansible-playbook -i inventory/staging.ini playbooks/deploy_app.ymlは、ターゲット環境を明示的に指定します。
  • 本番環境のinputブロックは、機密性の高いデプロイの前に手動ゲートを追加します。
  • 本番環境ではホストキーチェックを無効にしないでください。代わりに、Jenkinsエージェントでknown_hostsを管理してください。

Ansibleプレイブックの例:Webアプリケーションのデプロイ

以下は、Java Webアプリケーション用の簡略化されたinventory/staging.iniplaybooks/deploy_app.ymlです。アーティファクト名、サービス管理、パスは異なる場合があります。

inventory/staging.ini

[web_servers]
web1.example.com
web2.example.com

[database_servers]
db1.example.com

[all:vars]
ansible_user=ubuntu

playbooks/deploy_app.yml

---
- name: Webアプリケーションのデプロイ
  hosts: web_servers
  become: yes
  vars:
    app_name: my-webapp
    app_path: /opt/{{ app_name }}
    app_port: 8080
    app_version: "{{ app_version | default('1.0.0') }}"

  tasks:
    - name: アプリケーションディレクトリの存在確認
      ansible.builtin.file:
        path: "{{ app_path }}"
        state: directory
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0755'

    - name: アプリケーションJARをターゲットにコピー
      ansible.builtin.copy:
        src: "target/{{ app_name }}-{{ app_version }}.jar"
        dest: "{{ app_path }}/{{ app_name }}.jar"
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0644'
      notify: restart app service

    - name: systemdサービスファイルの存在確認
      ansible.builtin.template:
        src: templates/my-webapp.service.j2
        dest: /etc/systemd/system/{{ app_name }}.service
        owner: root
        group: root
        mode: '0644'
      notify: restart app service

    - name: アプリケーションサービスの起動と有効化
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: started
        enabled: yes

  handlers:
    - name: restart app service
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: restarted
        daemon_reload: yes

templates/my-webapp.service.j2(Systemdサービステンプレート)

[Unit]
Description={{ app_name }} Application
After=network.target

[Service]
User={{ ansible_user }}
ExecStart=/usr/bin/java -jar {{ app_path }}/{{ app_name }}.jar --server.port={{ app_port }}
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

このプレイブックは、アプリケーションディレクトリを作成し、ビルドされたJARをコピーし、systemdユニットを書き込み、サービスを開始します。ハンドラーは、アーティファクトまたはユニットファイルが変更された場合にのみサービスを再起動します。

ベストプラクティスとヒント

  • プレイブックを冪等に保ち、失敗したパイプラインを再実行しても追加の損害が発生しないようにします。
  • VaultパスワードをJenkinsの認証情報に保存し、一時的なパスワードファイルまたは承認された認証情報バインディングを介して渡します。
  • デプロイメントロジックをwebserverdatabaseapp_deployなどのロールに整理します。
  • ステージングと本番環境には、個別のインベントリまたはgroup_varsを使用します。
  • コントローラーではなく、専用のJenkinsエージェントでAnsibleを実行します。
  • 詳細が必要な場合のみ-vまたは-vvを使用し、ログに機密情報が漏洩しないようにします。
  • Jenkinsで実行する前に、特に本番ロールのプレイブックをテストします。

専門家に相談すべきタイミング

パイプラインが本番環境にデプロイする場合、共有認証情報を管理する場合、多くのホストに書き込む場合、または監査制御が必要な場合は、Jenkinsまたはプラットフォーム管理者を招いてください。認証情報の処理、ホストキーの検証、ロールバック動作は、初回の本番実行前に再レビューする価値があります。

まとめ

Jenkinsをオーケストレーションに、Ansibleをデプロイメント状態に使用します。認証情報はJenkinsに、プレイブックはバージョン管理に、インベントリは明示的に、そして本番デプロイを自動化する前に、パイプライン外で同じAnsibleコマンドをテストしてください。