cloud-initでDockerをセットアップする

## 概要

[Cloud-init](https://cloud-init.io/) を使ったDockerのセットアップについて記載します。
備忘録のため、参照する際は公式情報も合わせてご確認ください。

Cloud-init とは、初回セットアップのためのユーザ作成や任意のコマンド実行などの処理をcloud-configというYAMLファイルで記述することができるツールです。

主にAWSのEC2やGoogle CloudのGCEなどクラウド環境で利用することができます。
ここでは、Dockerをセットアップする際の記述をGCEの構築手順を例に示します。

## Dockerをセットアップするcloud-config

本題のcloud-configは以下のよう記述できます。

```yaml
#cloud-config
timezone: Asia/Tokyo
locale: ja_JP.utf8

# Dockerアプリ起動用ユーザ
users:
  - name: www
    groups: docker
    shell: /bin/bash
groups:
  - docker

# install docker
# 公式手順からの移植です https://docs.docker.com/engine/install/ubuntu/
package_update: true
package_upgrade: true

packages:
  - apt-transport-https
  - curl

runcmd:
  - install -m 0755 -d /etc/apt/keyrings
  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
  - chmod a+r /etc/apt/keyrings/docker.asc
  - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
  - apt-get update
  - apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  - systemctl enable docker
  - systemctl start docker
```

## GCE インスタンスでの構築例

先程のcloud-configを使って、GCEインスタンスのセットアップをする例を記載します。
リソースの作成はTerraformの利用を想定します（Terrafrom自体の説明は割愛します）。
また、Google Cloud に関する設定は本題ではないため省略しているため、実際に構築するには各所読み替えが必要になります。

ディレクトリ構成は、以下のようなものを想定しています。
```
.
├── terraform.tf
├── locals.tf
├── os_image.tf
├── test_instance.tf
└── templates
    └── cloud-init.yaml
```


```terraform
# terraform.tf
terraform {
  required_version = "~> 1.9.5"
  # https://registry.terraform.io/providers/hashicorp/google/latest
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 6.5.0"
    }
  }
}

provider "google" {
  # お好みで設定
  project        = "<略>"
  region         = "<略>"
  zone           = "<略>"
  default_labels = "<略>"
}
```

ローカル変数を定義
```terraform
# locals.tf
locals {
  vpc_id           = "<略>"
  subnet_id        = "<略>"
  gce_zone         = "asia-northeast1-a"
  gce_machine_type = "e2-micro"
  gce_network_tags = [""] # SSHするためにはFWルールが必要
  gce_disk_size    = 30 # 検証なので最低限
  gce_disk_type    = "pd-ssd"
  gce_os_family    = "ubuntu-2404-lts-amd64"
  gce_os_project   = "ubuntu-os-cloud"
}
```

OSイメージはUbuntuを利用
```terraform
# os_image.tf
data "google_compute_image" "this" {
  family  = local.gce_os_family
  project = local.gce_os_project
}
```

GCEインスタンスを設定
```terraform
# test_instance.tf
data "google_project" "this" {
  project_id = "<略>"
}

resource "google_compute_instance" "test" {
  name         = "<略>"
  machine_type = local.gce_machine_type
  zone         = local.gce_zone
  project      = data.google_project.this.project_id
  tags         = local.gce_network_tags
  boot_disk {
    initialize_params {
      image = data.google_compute_image.this.self_link
      size  = local.gce_disk_size
      type  = local.gce_disk_type
    }
  }
  network_interface {
    network            = local.vpc_id
    subnetwork         = local.subnet_id
    subnetwork_project = data.google_project.this.project_id
    stack_type         = "IPV4_ONLY"
    # 外部IPは不要なので明示的にコメントアウトしてます
    # access_config {}
  }
  service_account {
    # サービスアカウントは専用で作成するのが望ましいが、検証なのでデフォルトを利用します
    email  = "${data.google_project.this.number}-compute@developer.gserviceaccount.com"
    scopes = ["cloud-platform"]
  }
  metadata = {
    google-logging-enabled    = true
    google-monitoring-enabled = true
    enable-oslogin            = true
    # templates/cloud-init.yaml にYAMLファイルを用意して、それを指定します。
    # ファイルの中身は前節の内容と同じです。
    user-data                 = file("./templates/cloud-init.yaml")
  }
  
  lifecycle {
    # Terraformで差分として検知されたくない項目を記述
    ignore_changes = [
      metadata["ssh-keys"],
      boot_disk["initialize_params"],
    ]
  }
}
```

## まとめ

簡単にですが、cloud-init でDockerをセットアップする方法について記載しました。
検証などでDocker入りのVMが欲しいことが繰り返しあるため[^1]、個人的にはcloud-initでまとめておくと便利かと思い、記事に残しました。

[^1]:1GCEだとCOS(Container-Optimized OS)などDockerがプリインストールされたVMイメージもありますが、検証用途だとやはりUbuntuの方が都合がいい場面が個人的には多いです。