❄️

Nix+Terraform で個人用のインフラ開発環境を作る

に公開

やること

次のようなTerraform開発環境を作成します。

# shellを立ち上げる
task staging:up # 本番の場合はtask production:up

# 特定のfeatureを起動
task staging:feature1 -- [terraform_command]

また、適度なレベルのCIも設定します。

やらないこと

Workload Identity Pool, ProviderなどのOIDC通信はやりません。個人なので。

どうやるか

  • Terraform CloudでLocalを選択
  • 開発環境をNix Developで構築
  • CLIをTaskfileで操作

なぜNix?

Dependency Hellで苦しんだ経験があるのでサンドボックス環境に閉じ込めたい。gcloudもTerraformも管理したくない!!

なぜTaskfile?

Typing面倒!!

準備

  1. HashiCorp Cloudのアカウント作成
  2. Google CloudなどのPaaSのProjectを作成
  3. Nixのインストール
  4. Taskfileのインストール

DevShellを作る

{
  description = "Development environment for Terraform project";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "aarch64-darwin";
      pkgs = nixpkgs.legacyPackages.${system};
      google_cloud_project = builtins.getEnv "GOOGLE_CLOUD_PROJECT";
      asciiArt = ''
              .__                                             __
        ______|  |__   __ __   ____    ______  ____    ____  |  | __
       /  ___/|  |  \ |  |  \ /    \  /  ___/ /  _ \ _/ ___\ |  |/ /
       \___ \ |   Y  \|  |  /|  | |  \\____ \ (  <_> )\  \___ |    <
      /______>|___|  /|____/ |__| |__//______>\____/  \_____>|__|__\.
      '';
    in {
      devShells.${system}.default = pkgs.mkShell {
        packages = [
          pkgs.go-task
          pkgs.google-cloud-sdk
          pkgs.terraform
          pkgs.tflint
        ];

        shellHook = ''
          echo "${asciiArt}"
          echo "✅ Following packages are available:"
          echo "- Google Cloud SDK: $(command -v gcloud)"
          echo "- Task: $(command -v task)"
          echo "- Terraform: $(command -v terraform)"
          echo "- TFLint: $(command -v tflint)"
          echo "🚀 Setup Google SDK:"
          gcloud auth login
          gcloud auth application-default login
          gcloud config set project "${google_cloud_project}"
          echo "✅ Project set to ${google_cloud_project}."
          echo "🎉 Setup steps finished. Consider running 'terraform init' next."
        '';
      };
    };
}

起動の流れ

  1. Taskfileがワークフローを発火
  2. nix developのビルドが走る
  3. flake.nixshellHookが起動する

解説

GoogleCloudのProjectの情報はDevShellを起動する段階で決めたいものです。今回は環境変数をWrapperのTaskfile経由で渡します。

次の部分がNixが環境変数から取得する部分です

google_cloud_project = builtins.getEnv "GOOGLE_CLOUD_PROJECT";

ここにTaskfile経由で流し込みます。TaskfileではGlobalな設定とtaskごとの設定を書けます。複数入力があるので後者を利用します。

version: '3'

tasks:
  up:
    env:
      GOOGLE_CLOUD_PROJECT: "staging環境"
    cmds:
      - nix develop --impure
    silent: true

shellHook はShellに入るタイミングで起動するスクリプトです。ちゃんとやるなら、entrypoint.shを作った方が良いと思います。ASCII Artはかっこいいので書いています。実用性?しらんな。

ちなみにですが、Google Cloudを使う場合は次の設定を入れておくのが無難です。自分の使用しているプロジェクトは「常に」確信を持っておきたいですよね。(100敗)

echo "🚀 Setup Google SDK:"
gcloud auth login
gcloud auth application-default login
gcloud config set project "${google_cloud_project}"

Taskfileでstaging, production用のコマンドを作る

起動時のスクリプトを考えてみます。愚直にやるとこうなりますかね。

export GOOGLE_CLOUD_PROJECT=ステージング環境 && nix develop --impure

さすがに冗長すぎるので設定をします。Taskfileはymlを分割可能です。これ次のように設定すれば、staging:uptasks/staging.ymlで定義されたuptaskが起動します。

version: '3'

env:
  NIXPKGS_ALLOW_UNFREE: 1

includes:
  staging: tasks/staging.yml
  production: tasks/production.yml

Terraformのコマンド入力を楽にする

Terraformのディレクトリ移動面倒ですよね。仕組みでディレクトリ移動が発生しないようにします。例えば、次の設定ではtask staging:feature1 -- [command]と入力するとディレクトリ移動しなくて良くなります。

  • tasks/staging.yml
version: '3'

tasks:
  feature1:
    dir: "environments/staging/feature1"
    cmds:
      - terraform {{ .CLI_ARGS}}
    silent: true

GitHub Actions

LocalからApplyするにしてもLintやfmtぐらいは掛けたいものです。とりあえずLintが通らないのは嫌なので追加します。長くなりすぎるのもどうかと思うので省いていますが、terraform validateterraform format -recursiveあたりはいれましょう。

tflint

name: Lint Terraform
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Setup TFLint
      uses: terraform-linters/setup-tflint@v4
      with:
        tflint_version: latest
    - name: Run TFLint
      run: tflint -f compact

ShellCheck

どうせいつか生えるのでShellも検証します。これはTerraformに限らずプロジェクトを作るときは常にやっておいた方が良いです。

name: Lint ShellScript

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  shellcheck:
    name: Run ShellCheck
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run ShellCheck
        uses: ludeeus/action-shellcheck@master

Dependabot

dependabotも入れておきましょう。

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"

  - package-ecosystem: "terraform"
    directory: "environments/staging/feature1"
    schedule:
      interval: "monthly"

  - package-ecosystem: "terraform"
    directory: "environments/production/feature1"
    schedule:
      interval: "monthly"

おわりに

環境構築の際の助けになれば幸いです!!

Discussion