🐡

AnsibleとTerraformを使った自動環境構築 - Dockerサービス

に公開

はじめに

クラウド環境でのインフラ構築とプロビジョニングは、現代のDevOpsの基本的な要素です。この記事では、TerraformとAnsibleを組み合わせて、さくらのクラウド上にDockerが利用可能なVM環境を自動的に構築する方法について解説します。特に、CI/CD環境での実行時に遭遇した問題とその解決策に焦点を当てます。

前提条件

  • さくらのクラウドのアカウントとAPIキー
  • Terraform(インフラのコード化)
  • Ansible(構成管理)
  • GitHub Actions(CI/CD)

構成の概要

今回の構成は以下のようになっています:

  1. Terraformを使用してさくらのクラウドにVMを作成

    • 最小構成(1コア、1GB RAM、20GB SSD)
    • SSH公開鍵認証のみを許可
    • パスワード認証は無効化
  2. Ansibleを使用してVMにDockerをインストール

    • Dockerサービスのセットアップ
    • 必要なパッケージのインストール
    • タイムゾーン設定(Asia/Tokyo)
  3. GitHub Actionsを使用して全体を自動化

    • 暗号化された環境変数管理
      • SSH秘密鍵
      • さくらのクラウドAPIキー
      • terraform outputで取得されるサーバIP
    • 段階的な実行(plan→apply→provision)
    • 不要になったリソースの自動クリーンアップ(destory)

Ansibleでのサーバープロビジョニング

VMを作成した後、Ansibleを使用して必要なソフトウェアと設定をインストールします。特に今回は、Dockerのインストールと設定に焦点を当てます。

プレイブックの基本構造

---
- name: Docker環境構築
  hosts: app_server
  become: yes
  vars:
    docker_users:
      - ubuntu

  tasks:
    - name: システムパッケージのキャッシュ更新
      apt:
        update_cache: yes
        cache_valid_time: 3600
      register: apt_update_result
      retries: 5
      delay: 20
      until: apt_update_result is success

    - name: 必要なパッケージをインストール
      apt:
        pkg:
          - apt-transport-https
          - ca-certificates
          - curl
          - gnupg
          - lsb-release
          - software-properties-common
          - python3-pip
          - python3-setuptools
          - python3-docker
          - docker-compose
        state: present
      register: apt_install_result
      retries: 5
      delay: 20
      until: apt_install_result is success

    # Docker インストール
    - name: Docker GPG キーの追加
      apt_key:
        url: https://download.docker.com/linux/ubuntu/gpg
        state: present

    - name: Dockerリポジトリの追加
      apt_repository:
        repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable"
        state: present

    - name: Dockerパッケージをインストール
      apt:
        pkg:
          - docker-ce
          - docker-ce-cli
          - containerd.io
        state: present
        update_cache: yes
      register: docker_install_result
      retries: 3
      delay: 20
      until: docker_install_result is success

    - name: ユーザーをdockerグループに追加
      user:
        name: "{{ item }}"
        groups: docker
        append: yes
      loop: "{{ docker_users }}"

    - name: Dockerサービスが起動し、有効になっていることを確認
      systemd:
        name: docker
        state: started
        enabled: yes

    # タイムゾーン設定
    - name: タイムゾーンをAsia/Tokyoに設定
      timezone:
        name: Asia/Tokyo

apt更新・インストールについて

さくらのクラウドのVM構築にはスタートアップスクリプトという機能が備わっており、そちらでapt関連やDockerのインストールも可能です。今回は元々はDockerを使用してアプリケーションを実行する方針を取りましたが、依存関係のあるソフトウェアを複数インストールするといった対応も想定していたため、Ansibleでの構築に統一しております。
また、スタートアップスクリプトとAnsibleの両方でaptの処理を行っていると、後者の処理で失敗するケースがあるため、その後のタスクに影響するのであればAnsibleに寄せるのがベターと思います。

遭遇した問題:Dockerサービスの起動失敗

プロジェクトの実装中に遭遇した最も重要な問題の一つは、CI環境でのDockerサービスの起動失敗でした。手元の環境では問題なく動作するのに、GitHub Actionsでのデプロイ時に以下のエラーが発生しました:

TASK [Dockerサービスが起動し、有効になっていることを確認] **********************
fatal: [xxx.xxx.xxx.x]: FAILED! => {"changed": false, "msg": "Unable to start service docker: Job for docker.service failed because the control process exited with error code.\nSee \"systemctl status docker.service\" and \"journalctl -xeu docker.service\" for details.\n"}

原因の調査

エラーの詳細を調査するために、systemctl status docker.servicejournalctlの出力を確認しました:

× docker.service - Docker Application Container Engine
     Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Sun 2025-04-06 23:33:37 JST; 3min 6s ago
   Duration: 30.568s
TriggeredBy: × docker.socket
       Docs: https://docs.docker.com
    Process: 5004 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock (code=exited, status=1/FAILURE)
failed to load listeners: no sockets found via socket activation: make sure the service was started by systemd

ただ、同じPlaybookを手動で実行すると問題なく動作することから、CI環境特有の問題、またはタイミングに関連する問題である可能性が高いと判断しました。
以下のような可能性が問題であったと推測しています。

  1. システムリソースの競合:初回起動時に複数のサービスが同時に初期化される
  2. 依存関係の初期化タイミング:docker.socketサービスの起動タイミングの問題
  3. ネットワーク関連の初期化:DNSやネットワーク設定が完全に初期化される前にDockerが起動を試みる

解決策:リトライメカニズムの導入

今回は時間的制約も特になく、サービス起動が最終的にされていればよいので、Dockerサービスの起動にリトライメカニズムを導入することで、このような一時的な問題が解消されるまで待機して再試行する対応を行いました。

- name: Dockerサービスが起動し、有効になっていることを確認
  systemd:
    name: docker
    state: started
    enabled: yes
  register: docker_service_result
  retries: 10      # 10回リトライ
  delay: 30        # 30秒間隔
  until: docker_service_result is success

この修正により、以下のメリットが得られました:

  1. 耐障害性の向上:一時的なサービス起動の問題が発生しても自動的に再試行
  2. CI環境での安定性向上:リソース競合や初期化のタイミング問題を吸収
  3. コードの可読性維持:複雑なエラーハンドリングなしで問題を解決

まとめ

TerraformとAnsibleを組み合わせたインフラ構築は非常に強力ですが、特にCI環境での自動化にはいくつかの落とし穴があります。今回の例では、Dockerサービスの起動において環境差分によって引き起こされる問題に遭遇しましたが、適切なリトライメカニズムを導入することで解決できました。

このアプローチは、クラウドインフラの構築だけでなく、あらゆる種類の自動化タスクに適用できる一般的な原則です。システムの一時的な不安定性に対する耐性を持たせることで、より堅牢な自動化パイプラインを構築できます。

参考リソース

Discussion