もうボタンをクリックする前に、インフラをコードで書こう

アプリケーションにはサーバーとデータベースが必要です。よくあるやり方は、クラウドプロバイダーのダッシュボードにログインし、何ページもクリックしてインスタンスタイプを選び、ストレージを設定し、ネットワークを構成し、手順を一つも漏らしていないことを祈る、というものです。ステージング環境でも同じことを繰り返し、本番環境で何が動いているかをチームメンバーに聞かれれば、スクリーンショットを送ることになります。

このワークフローは、うまくいかなくなるまでは機能します。チェックボックスを一つ見逃したり、リージョンを間違えたり、セキュリティグループのルールを一つ忘れたりするだけで、アプリケーションは動かないか、意図しない脆弱性を抱えたまま動くことになります。

もっと良い方法があります。インフラをコードとして書くことです。

Infrastructure as Codeの本当の意味

Infrastructure as Code(IaC)とは、サーバー、データベース、ネットワーク、その他のクラウドリソースを、UIをクリックする代わりにテキストファイルで定義するプラクティスです。これらのファイルは、インフラの望ましい状態(desired state)を記述します。バージョン管理に保存し、アプリケーションコードと同じようにレビューし、環境を問わず一貫して適用します。

重要なのは、「ステップ1: サーバーを作成、ステップ2: 待機、ステップ3: データベースを作成」といったスクリプトを書くのではないということです。代わりに、最終的な状態がどうあるべきかを宣言します。ツールがリソース間の依存関係を解析し、操作の順序を自動的に決定します。

最初の設定を書く

Terraformは最も広く使われているIaCツールの一つです。.tf拡張子の設定ファイルを記述します。各ファイルには、必要なリソースとその接続方法を宣言します。

まずはmain.tfというファイルを作成します。最初に定義するのはプロバイダーです。プロバイダーは、TerraformがAWS、Google Cloud、Azureなどの特定のプラットフォームと通信するためのプラグインです。リソースを作成する場所と使用する認証情報をTerraformに伝えます。

provider "aws" {
  region = "ap-southeast-1"
}

プロバイダーを設定したら、リソースを定義します。すべてのリソースはresource "type" "local_name"というパターンに従います。ローカル名は設定ファイル内でのみ使用され、クラウドプロバイダーのコンソールに表示される名前ではありません。

以下は、AWSがEC2インスタンスと呼ぶ仮想サーバーを作成する例です。

resource "aws_instance" "app_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"

  tags = {
    Name = "app-server"
  }
}

そして、RDSを使ったPostgreSQLデータベースの例です。

resource "aws_db_instance" "app_database" {
  allocated_storage    = 20
  engine               = "postgres"
  engine_version       = "15"
  instance_class       = "db.t3.micro"
  db_name              = "myapp"
  username             = "admin"
  password             = var.db_password
  skip_final_snapshot  = true
}

重要な点に注目してください。手順を書いていません。「最初にサーバーを作成し、準備ができるまで待ち、その後データベースを作成する」とは書いていません。欲しいものを宣言しただけです。Terraformが設定を解析し、データベースがサーバーに依存していないことを判断し、それらを並行して作成します。もし一方のリソースが他方に依存していれば、Terraformが自動的に順序を調整します。

なぜ最初は重く感じるのか

設定ファイルを書くことは、ダッシュボードをクリックするよりも最初は時間がかかります。正確なリソースタイプ、必須の引数、ファイルの構造を知っている必要があります。ダッシュボードにはドロップダウンメニューやチェックボックスが表示されますが、設定ファイルではami-0c55b159cbfafe1f0が何を意味するのか、db.t3.microが何を指すのかを理解していることが求められます。

しかし、この初期コストは、セットアップを複製する必要が生じた瞬間に報われます。本番環境をミラーリングしたステージング環境が欲しいですか?ファイルをコピーし、いくつかの値を変更して、同じ設定を実行するだけです。どんなインフラが動いているのか知りたいですか?コンソールを探し回る代わりにファイルを読めば済みます。変更が適用される前にレビューしたいですか?プルリクエストを開き、チームが差分にコメントできるようにします。

設定をバージョン管理に保存する

.tfファイルはGitリポジトリに置くべきです。アプリケーションコードと同じリポジトリでも、別のインフラ用リポジトリでも構いません。いずれにせよ、インフラへのすべての変更は、コード変更と同じワークフロー(書き込み、コミット、レビュー、マージ、適用)を経由します。

これにより、いくつかの利点が得られます。

  • すべてのインフラ変更が、誰がなぜ行ったかとともに記録されます。
  • 問題が発生した場合、以前の状態にロールバックできます。
  • 新しいチームメンバーは、リポジトリを読むだけでインフラ全体の構成を把握できます。
  • 自動チェックにより、本番環境に到達する前に設定を検証できます。

あなたがやっていないこと

Infrastructure as Codeを書くとき、あなたはデプロイスクリプトを書いているわけではありません。スクリプトはコマンドを順番に実行します。ステップ3で失敗するとスクリプトは停止し、中途半端に作成されたインフラが残ります。TerraformのようなIaCツールは宣言的です。現在のインフラの状態と設定ファイルに記述された望ましい状態を比較し、その状態に到達するために必要な変更のみを行います。

この違いは、インフラを更新する必要があるときに重要になります。現在の状態を考慮した新しいスクリプトを書く代わりに、設定ファイルを更新します。Terraformが何を追加、変更、削除すべきかを自動的に判断します。

実践的なワークフロー

Terraformの典型的なワークフローは、write(記述)、plan(計画)、apply(適用)の3つのフェーズで構成されます。

まず、.tfファイルを記述または修正します。ここで、望むインフラを定義します。

次に、terraform planを実行します。このコマンドは、実際に何かを行うことなく、Terraformが何をするかを表示します。詳細な差分が出力されます。どのリソースが作成され、どのリソースが変更され、どのリソースが削除されるかがわかります。この計画を確認してから次に進みます。

最後に、terraform applyを実行して計画を実行します。Terraformはクラウドプロバイダーに対してAPI呼び出しを行い、実際の状態が設定と一致するまでリソースを作成、更新、または削除します。

最初のIaCセットアップのためのクイックチェックリスト

  • クラウドプロバイダーを選び、Terraform CLIをインストールする。
  • プロバイダーブロックを含むmain.tfファイルを作成する。
  • 仮想マシンやデータベースなど、少なくとも1つのリソースを定義する。
  • terraform initを実行してプロバイダープラグインをダウンロードする。
  • terraform planを実行して、何が作成されるかを確認する。
  • terraform applyを実行してリソースを作成する。
  • すべての.tfファイルをGitリポジトリに保存し、プッシュする。

本当の価値は後から現れる

Infrastructure as Codeを書くことは、初日には余分な作業に感じられます。しかし30日目、新しい環境を追加したり、ミスから復旧したりする必要が生じたとき、これが唯一のまともな作業方法だと感じるでしょう。設定ファイルがインフラの唯一の情報源(single source of truth)になります。本番環境で何が動いているか推測する必要はもうありません。誰かが忘れるかもしれない手動のステップもありません。再現方法を知っているのがたった一人だけというインフラもありません。

まずは一つのリソースから始めましょう。それをファイルに書き、Gitに入れます。そして次のリソースを追加します。この習慣は思ったより早く身につき、それがもたらす自信は最初の摩擦を補って余りあるものです。