さくらのクラウド+Container LinuxでPXEブート+Ignitionを使う

f:id:febc_yamamoto:20190216160328p:plain

さくらのクラウド + Container Linuxでのプロビジョニング

さくらのクラウドでContainer Linuxを使う場合、プロビジョニングをどうするかは悩みどころです。

さくらのクラウドではプロビジョニングのための仕組みとしてスタートアップスクリプトは用意されているものの、Container Linuxでは利用できないという制限があります。

参考 スタートアップスクリプトは以下のOSに対応しています。
- CentOS
- Debian
- Ubuntu
- FreeBSD
- RancherOS
- VyOS

スタートアップスクリプト以外でプロビジョニングするには?

スタートアップスクリプト以外でプロビジョニングする方法としては以下のようなものが挙げられます。

  • Ansibleなどのプロビジョニングツールを利用してサーバ起動後にSSHで操作
  • ISOイメージ機能を利用してConfigDrive経由でcloud-configを流し込む
  • PXEブート環境を構築しIgnitionを使う

2番目のISOイメージでConfigDriveを使う方法についてはこちらの記事が詳しいです。

hnakamur.github.io

3番目の方法は、前回紹介したcybozuさんのsabakanやCoreOS社の提供するMatchboxを使ってDHCPサーバやIgnitionファイルの管理/配布などを行う環境を作るというもので、導入/運用の敷居が少し高めとなっています。

参考: sabakanの紹介記事

febc-yamamoto.hatenablog.jp

もうちょっと楽にPXEブート+Ignitionの管理ができないか、、、

ということで今回の本題の「terraform-sakuracloud-matchbox」というモジュールを公開しました。

Terraform + Matchbox + さくらのクラウド = terraform-sakuracloud-matchbox

terraform-sakuracloud-matchboxはMatchboxを手軽に導入するためのTerraformモジュールです。

registry.terraform.io

以下のようなtfファイルの記述でMatchboxをセットアップすることが可能です。

module "matchbox" {
  source =  "sacloud/matchbox/sakuracloud"

  #Matchboxを稼働させるサーバのパスワード
  server_password = "put-your-server-password"

  #DHCPサーバなどのPXEブート関連機能を動かすためのネットワークに接続するスイッチのID
  switch_id       = "1234556789012"

}

このモジュールは以下のような動きをします。

  • サーバを1台作成
  • サーバをインターネット(共有セグメント)と指定されたスイッチに接続
  • サーバ上でMatchboxとdnsmasqを起動
  • スイッチ側NIC上でMatchboxのHTTP APIエンドポイントを提供
  • DHCPクライアントに対するデフォルトゲートウェイとして構成(ip_forward)
  • MatchboxのgRPC API用にCA証明書、サーバ証明書/秘密鍵、クライアント証明書/秘密鍵を生成(オプションで指定も可能)
  • インターネット側NIC上でMatchboxのgRPCエンドポイントを提供
  • CoreOSの起動/インストール用イメージなどのアセットの提供

f:id:febc_yamamoto:20190216165154p:plain

このモジュールはMatchboxサーバを提供するのみで、接続するスイッチ(VPC)やPXEブートするサーバなどは別途用意する必要があります。
また、Matchbox自体への設定の投入(IgnitionやMetadataなど)も別途用意する必要があります。

次にこのモジュールを使って実際にPXEブートしてContainerLinuxをブート/ディスクへのインストールまでを行ってみます。

terraform-sakuracloud-matchboxでContainerLinuxの起動/プロビジョニングまで

実際にどうやって使うのかをみていきます。

準備

以下のものが必要になります。あらかじめ準備しておいてください。

さくらのクラウド/Matchbox向けのTerraformプロバイダーはCommunity ProviderでTerraform本体とは別で配布されており個別にインストールが必要です。
それぞれの最新版をGitHubからダウンロードし、~/.terraform.d/plugins/配下などに配置しておいてください。 詳細は以下のドキュメントを参照してください。

www.terraform.io

さくらのクラウドAPIキーは環境変数などに設定しておいてください。

sacloud.github.io

なお、今回は簡便のためモジュールわけなどせずに同じディレクトリ内にMatchboxサーバの定義とPXEブートするサーバ/Ignitionの定義などを格納します。
本来はライフサイクルが異なるため別モジュールに分けるべきと思いますので、実際に利用する際はこの辺りも考慮しておいてください。

Matchbox構築

まずはMatchboxを動かすためのサーバとPXEブートで利用するスイッチを作成します。

以下のようなtfファイルmatchbox.tfを作成します。

resource sakuracloud_switch "switch" {
  name = "matchbox-internal"
}

module "matchbox" {
  source             = "sacloud/matchbox/sakuracloud"

  server_password    = "your-password"
  switch_id          = "${sakuracloud_switch.switch.id}"

  server_public_key  = "${file("~/.ssh/id_rsa.pub")}"
  server_private_key = "${file("~/.ssh/id_rsa")}"
}

パスワードは適当なものに置き換えてください。 また、サーバに接続するためのSSH公開鍵/秘密鍵も合わせて指定しています。
~/.ssh/id_rsa~/.ssh/id_rsa.pubが存在しない場合は適当にssh-keygenなどで作成しておいてください。

作成したらterraform initしてterraform applyします。

$ terraform init
...
Terraform has been successfully initialized!
...

$ terraform apply

...
Apply complete! Resources: 17 added, 0 changed, 0 destroyed.

これでサーバが作成されMatchboxが動いている状態となりました。

PXEブートするサーバの作成/Ignitionなどの定義

次にPXEブートするサーバやサーバに適用するIgnitionの定義を行います。

サーバの定義

まずはサーバの定義を行います。以下のようなtfファイルservers.tfを作成してください。

locals {
  node_count = 1
}

resource sakuracloud_server "server" {
  name   = "node-${count.index+1}"
  core   = 2
  memory = 2 # Container Linuxは2GB以上のメモリが必要
  nic    = "${sakuracloud_switch.switch.id}" # 先ほど作成したスイッチに接続
  disks  = ["${sakuracloud_disk.disk.*.id[count.index]}"]

  count  = "${local.node_count}"
}

resource sakuracloud_disk "disk" {
  name  = "node-${count.index}"
  size  = 20
  count = "${local.node_count}"
}

注意点は以下2つです。

  • サーバのメモリは2GB以上にする(Container Linuxの要件)
  • サーバのNICは先ほど作成したスイッチに接続する

本来はディスクの作成時にコピー元アーカイブの指定やサーバへのIPアドレスの指定などが必要なのですが、 今回はOSの入っていないブランクディスク、設定類はIgnitionで行うためこの指定となっています。

次にMatchboxで配布するIgnitionなどの定義を行います。

Matchboxの設定

Matchboxプロバイダーへのパラメータ指定

Ignitionなどの定義はMatchboxが提供するgRPC APIを用いて行います。
これをTerraformから行えるようにしてくれるのがTerraformのMatchboxプロバイダーです。

まずは先ほど作成したMatchboxサーバを利用するようにMatchboxプロバイダーへのパラメータ指定を行います。

以下のようなtfファイルをprovider_matchbox.tfという名前で作成してください。

provider "matchbox" {
  endpoint    = "${module.matchbox.matchbox_grpc_api_endpoint}"
  client_cert = "${module.matchbox.client_cert}"
  client_key  = "${module.matchbox.client_key}"
  ca          = "${module.matchbox.ca_cert}"
}

接続するための情報は先ほどのterraform-sakuracloud-matchboxモジュールが提供してくれますので、tfファイル上でモジュールの値を参照するように指定しています。

MatchboxのProfile/Group作成

次にMatchboxのProfileとGroupを作成します。

MatchboxでのProfileとは名前付きの設定テンプレートのことです。
Profileは iPXEやGRUB、Ignition configなどを保持します。

GroupとはMatchboxクライアントから提示されるMACアドレスやシリアル、UUIDなどを用いて クライアントとプロファイルを結びつけるものです。

coreos.com

と言ってもよくわからないですよね。実物を見るのが早いと思います。

まずProfileとして

  • PXEブートした時に適用するProfile
  • ディスクにOSをインストールした後に適用するProfile

の2種類を作成します。

以下のようなtfファイルをprofile.tfとして作成します。

UPDATE: 2019/2/17 00:25 argsの誤りを修正

// Create a CoreOS-install profile
resource "matchbox_profile" "coreos-install" {
  name = "coreos-install"

  kernel = "${module.matchbox.matchbox_assets_coreos_kernel_path[0]}"
  initrd = ["${module.matchbox.matchbox_assets_coreos_initrd_path[0]}"]
  args = [
    "initrd=coreos_production_pxe_image.cpio.gz",
    "coreos.config.url=${module.matchbox.matchbox_http_api_endpoint}/ignition",
    "coreos.first_boot=yes",
    "ipv6.disable=1",
    "console=tty0",
    "console=ttyS0",
  ]

  container_linux_config = <<EOF
---
systemd:
  units:
    - name: installer.service
      enable: true
      contents: |
        [Unit]
        Requires=network-online.target
        After=network-online.target
        [Service]
        Type=simple
        ExecStart=/opt/installer
        [Install]
        WantedBy=multi-user.target
storage:
  files:
    - path: /opt/installer
      filesystem: root
      mode: 0500
      contents:
        inline: |
          #!/bin/bash -ex
          curl --retry 10 "{{.ignition_endpoint}}?os=installed" -o ignition.json
          coreos-install -d /dev/vda -C stable -V current -i ignition.json {{if index . "baseurl"}}-b {{.baseurl}}{{end}}
          udevadm settle
          systemctl reboot
passwd:
  users:
    - name: core
      ssh_authorized_keys:
        - {{.ssh_authorized_key}}
EOF
}

// Create a simple profile which just sets an SSH authorized_key
resource "matchbox_profile" "simple" {
  name                   = "simple"
  container_linux_config = <<EOF
---
passwd:
  users:
    - name: core
      ssh_authorized_keys:
        - {{.ssh_authorized_key}}
EOF
}

ちょっと長いですが順番にみていきましょう。

まずPXEブートした時に適用するProfileがmatchbox_profile.coreos-installです。
ここではiPXEブート時に必要になるvmlinuzとinitrdのダウンロードURL、起動時のカーネルパラメータ、起動時に渡すcontainer_linux_config(Ignition)の内容を指定しています。

container_linux_configではcoreos-installコマンドを用いたディスクへのOSインストールとユーザーcoreの作成、SSH用の公開鍵の登録を行っています。

次にディスクにOSをインストールした後に適用するProfileがmatchbox_profile.simpleです。
こちらはユーザーcoreの作成とSSH用の公開鍵を登録しているだけです。

ここでcontainer_linux_configの中で{{.xxx}}のようなパラメータ指定をしているのに気づかれた方もいらっしゃるかもしれません。
これは後述するGroupから渡されるパラメータです。Profileとしては設定のテンプレートを用意しておき、Groupから渡されるパラメータを反映することで具体的なcontainer_linux_config(Ignition)の内容となるということですね。

次にGroupです。
以下のようなtfファイルをgroup.tfとして作成します。

// Default matcher group for machines
resource "matchbox_group" "default" {
  name    = "default"
  profile = "${matchbox_profile.coreos-install.name}"

  # セレクタ未指定のため、パラメータ指定のない全てのマシンにマッチする

  metadata {
    ignition_endpoint  = "${module.matchbox.matchbox_http_api_endpoint}/ignition"
    ssh_authorized_key = "${file("~/.ssh/id_rsa.pub")}"
    baseurl            = "${module.matchbox.matchbox_http_api_endpoint}/assets/coreos"
  }
}

// Match machines which have CoreOS Container Linux installed
resource "matchbox_group" "node1" {
  name    = "node1"
  profile = "${matchbox_profile.simple.name}"

  selector {
    os = "installed"
  }

  metadata {
    ssh_authorized_key = "${file("~/.ssh/id_rsa.pub")}"
  }
}

上の方がPXEブート時に利用されるGroup、下の方がディスクにOSインストール後に利用されるGroupです。 先ほどのProfile定義では以下のような記述がなされていました。

  • PXEブート時のカーネルパラメータ: coreos.config.url=${module.matchbox.matchbox_http_api_endpoint}/ignition
  • coreos-installコマンドに渡すIgnition.jsonの取得コマンド: curl --retry 10 "{{.ignition_endpoint}}?os=installed" -o ignition.json

どちらもMatchboxサーバの/ignitionに対してHTTPでリクエストしてignition.jsonを取得しています。
後者はパラメータとしてos=installedが指定されていますので、これを元にどののGroupなのか? -> どのProfileなのか? -> どのignition.jsonを返すか、をMatchboxが判定してくれます。

ここまできたらあとはterraform applyを実行するだけです。

applyするとブランクディスクからサーバが作成され、PXEブート〜ディスクへのOSインストールが行われるはずです。

Profileの定義を変えることで様々なプロビジョニングが行えますね!

その他の機能: OS起動/インストールイメージのプリフェッチ

デフォルトではContainerLinuxのPXEブート/ディスクへのインストール用イメージをMatchbox上にプリフェッチするようになっています。
以下のパラメータを指定することでプリフェッチを無効化したり、異なるバージョンのイメージをプリフェッチしておくことが可能です。

  # 対象のContainerLinuxのバージョン("1967.6.0"みたいに指定する)
  prefetch_coreos_assets_keys = ["current"]

  # 対象のChannel(https://coreos.com/releases/を参照)
  prefetch_coreos_assets_channel = "stable" # allowed values are: current/beta/alpha/custom

  # カスタムイメージを使う場合のダウンロードURL
  prefetch_coreos_assets_custom_url = ""

デフォルトはstableチャンネルのcurrentバージョンをプリフェッチします。
alphabetaも利用可能です。
prefetch_coreos_assets_keysに空を指定することでプリフェッチを無効化可能です。

なお、プリフェッチを無効化した場合はMatchboxのProfile/Groupで各種ダウンロード先を変え忘れないように注意してください。

その他の機能: ゲートウェイ機能のON/OFF

デフォルトではMatchboxサーバはIPフォワードが有効化され、DHCPクライアントからの外部へのリクエストをSNATするようになっています。

この挙動は以下のパラメータで制御可能です。

  # フォワードの有効/無効
  server_enable_forward = true

例えば既存のVPC内に配置したり、デフォルトゲートウェイとしてVPCルータを利用すると言った場合にはこの設定を変更する必要があります。

また、この設定を変えた場合、DHCPで配布するデフォルトゲートウェイの設定も変える必要があります。
以下のあたりのパラメータを合わせて変更してください。

  # Matchboxサーバ自身のプライベート側IPアドレス
  matchbox_ipaddress     = "192.168.0.1"

  # Matchboxサーバのネットワークマスク長
  matchbox_nw_mask_len   = "24"

  # MatchboxのHTTP APIが利用するポート番号
  matchbox_http_api_port = 8080

  # MatchboxのgRPC APIが利用するポート番号
  matchbox_grpc_api_port = 8081

  # DHCPで配布するデフォルトゲートウェイのアドレス
  matchbox_gateway       = "192.168.0.1"

  # DHCPで配布するアドレス範囲
  dhcp_start             = "192.168.0.101"
  dhcp_end               = "192.168.0.200"

終わりに

このモジュールを利用することでTerraform + MatchboxでPXEブート + Ignitionの管理が行えるようになります。
Terraformで完結していますので既存のTerraformを用いたワークフローにも適合しやすいと思います。
ぜひ使ってみてください。

以上です。