さくらのクラウドでcloud-initを使ってみた

本記事は、[さくらインターネット Advent Calendar2024](https://qiita.com/advent-calendar/2024/sakura) 25日目の記事です。

さくらのクラウドでcloud-initを使ってみたので、学んだことを利用例と合わせて記載します。

## cloud-initって何？

cloud-initは、クラウド環境においてインスタンスの初期設定を自動化するツールです。

cloud-initを使うと、以下のようなことができます。

* ユーザーの作成
* SSH鍵の設定
* パッケージのインストール
* ホスト名の設定
* ネットワークの設定
* スクリプトやコマンドの実行

他にもいろいろなことができます。
詳しいことは[公式ドキュメント](https://cloudinit.readthedocs.io/en/latest/index.html)を参照ください。

さくらのクラウドでも、cloud-initを使ってサーバーの起動時に初期設定を行うことができます。

## cloud-initを使ってみる

[cloud-init | さくらのクラウド マニュアル](https://manual.sakura.ad.jp/cloud/server/cloud-init.html#id8)を参考に使ってみます。

さくらのクラウドでcloud-initを使うには、名前の末尾に「(cloudimg)」が付くパブリックアーカイブを選ぶ必要があります。
この記事では、「Ubuntu Server 20.04.6 LTS 64bit (cloudimg)」を選びます。

追加ユーザデータには、以下を入力します。

```yml:user_data
password: $6$rounds=4096$aUONx5Ru8eTS$a9QMv8zx6C7DQXMvtYeJqxzeCyQ9KF6Aap.mGXECmADcNo/Qe9jWFABKfOWFQojr/grq3rdzNKGvJIzo1ekuU.
```

これは、デフォルトユーザーのパスワードを`password`にするための設定です。

サーバーが起動したら、以下でログインできるようになっているかと思います。

* ユーザー名：`ubuntu`
* パスワード：`password`

## Terraformからcloud-initを使ってみる

[Terraform for さくらのクラウド(v2)](https://docs.usacloud.jp/terraform/)では、`sakuracloud_server`の`user_data`にユーザデータを与えることでcloud-initを使うことができます。
詳しくは[サーバ - Terraform for さくらのクラウド(v2)](https://docs.usacloud.jp/terraform/r/server/)を参照ください。

例えば、以下のようなコードで、cloud-initで初期設定を行うサーバーを構築することができます。

```tf:main.tf
data "sakuracloud_archive" "ubuntu_cloudimg" {
  filter {
    tags = [
      "cloud-init",
      "distro-ubuntu",
    ]
  }
}

resource "sakuracloud_disk" "root" {
  name              = "test"
  description       = "Root volume"
  plan              = "ssd"
  size              = 20
  source_archive_id = data.sakuracloud_archive.ubuntu_cloudimg.id

  lifecycle {
    create_before_destroy = true
    ignore_changes = [
      source_archive_id,
    ]
  }
}

resource "sakuracloud_server" "test" {
  name        = "test"
  core        = 1
  description = "Test server"
  disks       = [sakuracloud_disk.root.id]
  memory      = 1

  user_data = templatefile("templates/cloud-config.yml.tftpl", {
    ssh_public_key = file("files/ssh_key.pub")
  })

  network_interface {
    upstream = "shared"
  }

  lifecycle {
    ignore_changes = [
      user_data,
    ]
  }
}
```

[Terraformのtemplatefile関数](https://developer.hashicorp.com/terraform/language/functions/templatefile)を使って`user_data`に`templates/cloud-config.yml.tftpl`というファイルの内容を入力しています。

`user_data`をignore_changesに設定しているのは、`user_data`を変更するたびにサーバーの再起動が発生するのを防ぐためです。
cloud-initを使うのがサーバー作成時のみの場合、意図しない再起動を防ぐために設定しておくのがおすすめです。

`templates/cloud-config.yml.tftpl`の内容は以下の通りです。

```yml:cloud-config.yml.tftpl
#cloud-config
locale: ja_JP.utf8
ssh_authorized_keys:
  - ${ssh_public_key}
ssh_pwauth: false
chpasswd:
  expire: false
password: $6$rounds=4096$aUONx5Ru8eTS$a9QMv8zx6C7DQXMvtYeJqxzeCyQ9KF6Aap.mGXECmADcNo/Qe9jWFABKfOWFQojr/grq3rdzNKGvJIzo1ekuU.
timezone: Asia/Tokyo
```

`${ssh_public_key}`にはtemplatefile関数で入力しているファイル`files/ssh_key.pub`の内容が記述されます。
サーバーを複数台まとめて構築する際など、サーバーごとに設定する公開鍵を変えたいときにTerraformのtemplateと組み合わせて使うと便利です。

## メタデータを参照する

cloud-initでは、インスタンスのメタデータを参照することができます。
さくらのクラウドで利用できるメタデータは、[cloud-init | さくらのクラウド マニュアル](https://manual.sakura.ad.jp/cloud/server/cloud-init.html#metadata)に記載されています。

メタデータはJinjaテンプレートの形式で参照することができます。

```yml:cloud-config.yml.tftpl
## template: jinja
#cloud-config
hostname: {{ ds.meta_data.Server.Name }}
locale: ja_JP.utf8
ssh_authorized_keys:
  - ${ssh_public_key}
ssh_pwauth: false
chpasswd:
  expire: false
password: $6$rounds=4096$aUONx5Ru8eTS$a9QMv8zx6C7DQXMvtYeJqxzeCyQ9KF6Aap.mGXECmADcNo/Qe9jWFABKfOWFQojr/grq3rdzNKGvJIzo1ekuU.
timezone: Asia/Tokyo
```

`ds.meta_data.Server.Name`からは、さくらのクラウド上でのサーバーの名前を取得できます。
そのため、上に記載したTerraformのコードとこのユーザデータを使ってサーバーを構築した場合、そのサーバーのホスト名は`test`に設定されます。

## cloud-initでNICを設定する

サーバーに接続しているネットワークインターフェースの情報もメタデータとして取得することができます。
そこで、cloud-initを使ってサーバーのネットワークインターフェースの設定を行ってみます。

```yml:cloud-config.yml.tftpl
## template: jinja
#cloud-config
bootcmd:
  - |
    if [ ! -e "/etc/netplan/60-netcfg.yaml" ]; then
    cat <<EOF > /etc/netplan/60-netcfg.yaml
    network:
      ethernets:
        lo:
          addresses:
            - 127.0.0.1/8
            - ::1/128
        ens3:
          addresses:
            - {{ ds.meta_data.Server.Interfaces[0].IPAddress }}/{{ ds.meta_data.Server.Interfaces[0].Switch.Subnet.NetworkMaskLen }}
          dhcp4: false
          nameservers:
            addresses:
            - 210.188.224.10
            - 210.188.224.11
          routes:
            - to: default
              via: {{ ds.meta_data.Server.Interfaces[0].Switch.Subnet.DefaultRoute }}
      renderer: networkd
      version: 2
    EOF
    netplan apply
    fi
hostname: {{ ds.meta_data.Server.Name }}
locale: ja_JP.utf8
ssh_authorized_keys:
  - ${ssh_public_key}
ssh_pwauth: false
chpasswd:
  expire: false
password: $6$rounds=4096$aUONx5Ru8eTS$a9QMv8zx6C7DQXMvtYeJqxzeCyQ9KF6Aap.mGXECmADcNo/Qe9jWFABKfOWFQojr/grq3rdzNKGvJIzo1ekuU.
timezone: Asia/Tokyo
```

ネットワークの設定は`bootcmd`で行っています。
ネットワークの設定ファイルが存在しない場合はネットワークの設定ファイルを書き出し、`netplan apply`を実行します。

`ds.meta_data.Server.Interfaces[0].IPAddress`で参照しているのは、サーバーに設定しているひとつめのNICに割り当てられたIPアドレスです。
上に記載したTerraformのコードを使ってサーバーを構築した場合、共有セグメントに接続されたNICのIPアドレスが取得できます。

共有セグメントに接続されたNICは、DHCPによって自動でIPアドレスが割り当てられますが、この機能を使っている場合はSLAの対象外になります。
そのため、共有セグメントに接続するNICをcloud-initで設定するなら、DHCPを無効化する設定も一緒に入れてしまうのがおすすめです。

> DHCPにてIPアドレスを取得し通信を行っている際は、SLAの対象外となります。
[DHCP | さくらのクラウド マニュアル](https://manual.sakura.ad.jp/cloud/server/dhcp.html)

## まとめ

さくらのクラウドでcloud-initを使ってサーバーの初期設定を行う方法を紹介しました。
ご参考になれば幸いです。

## 参考

* [さくらのクラウドでのcloud-initの実装と利用例の紹介 / Implementation of cloud-init in SAKURA Cloud and introduction of usage examples - Speaker Deck](https://speakerdeck.com/komei22/sakuranokuraudodenocloud-initnoshi-zhuang-toli-yong-li-noshao-jie)
* [さくらのクラウドのcloud-initでネットワークの設定を行う方法(2024/03) | SUPER-G.EEK.JP](https://super-g.eek.jp/posts/2024/03/12/sakuracloud-network-settings-used-by-cloud-init/)