IaCいいですよね。
なのでこの記事ではTerraform-provider-esxiを使って、おうちESXIをTerraformで管理してVMを立ち上げ、cloud-initを活用してTeleportを自動セットアップします。 子ノードを作成して接続まで一応するのですが、Teleportはノードの接続にinviteトークンを発行する必要があり、そこだけ自動化できていません。(今後の課題)
Terraformについては過去の記事GithubActions + TerraformでAWS開発を、TeleportについてはDockerでお手軽にTeleportを試すも参考にしてください。
terraform-provider-esxi
terraformはproviderと呼ばれるいわゆるプラグインを用いて、様々なサービス等と接続できます。 今回はterraform-provider-esxiという有志の方が作ってくれているproviderを使います。 ミニマムにまとまっていて使いやすいのですが、安定性はちょっと微妙かもしれないです。趣味なら全然使えるという感じですね。
hashicorp社が公式に出しているvsphere-providerというのもあって、vsphere越しにESXIを使えて良さげなのですがそもそもvsphereはすごいお値段がするので個人で所有するのはちょっと…という感じですね。 このproviderで、vsphere抜きにしてESXIだけ触れたら超最高なのですが、そう上手くはいかないですね。
この記事ではコードの断片的な部分しか載せていないので最終形はGithubのレポジトリを参照してください。
前準備
ESXIがインストールされたマシンはもちろんとして、ホストsshが有効になっている必要があります。
とりあえずVMを起動
自分でosをインストールするのは大変すぎるのでOVAファイルをもとにセットアップします。
ubuntuのOVAファイルはUbuntu Cloud Imagesから手に入ります。ダウンロードしてmain.tfと同じディレクトリに入れておいてください。
main.tfファイルと、tfvarsファイルを作ります(一応)。
main.tf
variable "esxi_host" {}
variable "esxi_user" {}
variable "esxi_password" {}
terraform {
required_providers {
esxi = {
source = "josenk/esxi"
version = "1.10.2"
}
}
}
provider "esxi" {
esxi_hostname = var.esxi_host
esxi_username = var.esxi_user
esxi_password = var.esxi_password
}
resource "esxi_guest" "teleport_proxy" {
guest_name = "teleport_proxy"
disk_store = "Data" # お使いの環境に合わせてください
virthwver = "11" # お使いの環境に合わせてください
power = "on"
boot_disk_size = 32
guestos = "ubuntu"
ovf_source = "./bionic-server-cloudimg-amd64.ova"
network_interfaces {
virtual_network = "playground" # お使いの環境に合わせてください
}
}
vars.tfvars
esxi_host = "*.*.*.*"
esxi_user = "*******"
esxi_password = "*******"
特に私がハマったのは、virthwver (仮想マシンのハードウェアバージョン)のデフォルトが、自分が使っているESXIが対応していないバージョンだった為に 変なところでterraformのapplyが失敗してしまって、tfstateとの動機が切れてしまうという問題です。きっちり指定してあげましょう。
できたら、初回のterraform initをして、その後plan, applyと進みます。
また、plan, applyでは作成したtfvarsを、--var-file vars.tfvars
と指定してあげます。
applyしてもりもり処理が走って、
apply complete! Resources: 1 added, 0 changed, 0 destroyed.
と表示されれば成功です!
cloud-initでユーザー作成
前章で無事VMが作成できたのはいいのですが、まずsshはできませんし、esxiのclientのコンソールでもログインプロンプトはでてるものの何を入れたらいいかわからないかと思います。だって設定してないですし。
というわけでcloud-initを使って設定していきます。
cloud-initはyaml形式でサーバーの初期設定をする仕組みです。
ユーザーとか、インストールパッケージとか、実行するコマンドとか、様々な実行項目が「モジュール」として定義されていて、モジュール単位で設定をぐるぐる練っていく感じです。 モジュール一覧は公式ドキュメントを見てください。
まずは設定を読み込む部分をtfファイルに書きます
main.tf(抜粋)
resource "esxi_guest" "teleport_proxy" {
...
guestinfo = {
"userdata" = base64gzip(file("./cloud-init.yaml"))
"userdata.encoding" = "gzip+base64"
}
}
terraformではfile関数で外部のファイルの文字列を取り込めます。便利。
では本体のcloud-init.yamlを作ります。
cloud-init.yaml
#cloud-config
users:
- name: testuser
plain_text_passwd: "password"
lock_passwd: false
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ssh-rsa AAAAAAAAAAAAAAA= MYHOSTNAME
先頭の#cloud-configが超重要です!!!
cloud-initはシェルスクリプトかcloud-init形式か選べて、先頭にこのコメントがあれば後者になるという仕組みです。 これをtypoしてるとうまくいきません。気をつけてください。
あと、大変申し訳無いのですが以後でてくるcloud-init.yamlは全部抜粋なのでこの行を省きます。この説明を見落とされないことを願います。
lock_passwdのデフォルト値はtrueなので、デフォルトだとパスワードログインができないところが注意ですね。
これで再度terraform applyすれば自動的にVMが 再生成 されます。((以前のは強制的に消されちゃいます) これでターミナルでパスワードログインしたり、sshで接続できたりするはずです(IPは頑張って調べてください)。
IP固定
サーバーとして使うならIPを固定したいですよね。 ネットワーク関連の設定は公式ドキュメントのここになります。
でもって、これらの設定はuserdataとは別枠で設定することになります。さっきcloud-init.yamlを追加したノリでmeta.yamlを追加します。
main.tf(抜粋)
resource "esxi_guest" "teleport_proxy" {
...
guestinfo = {
"userdata" = base64gzip(file("./cloud-init.yaml"))
"userdata.encoding" = "gzip+base64"
"metadata" = base64gzip(file("./meta.yaml"))
"metadata.encoding" = "gzip+base64"
}
}
meta.yaml
network:
version: 1
config:
- type: physical
name: ens32
subnets:
- type: static
address: 0.0.0.0/0 # 設定したい固定IP
gateway: 0.0.0.0 # おうちのGW
- type: nameserver
address:
- 0.0.0.0 # おうちのDNS
でもって例によってterraform applyすれば再生成されて指定したIPで新しく生まれてくるはずです。
teleportをセットアップ
ではteleportをインストールしていきましてよ。
基本的にパッケージをインストールするのは、packageモジュールにパッケージ名を列挙するだけなんですけど、teleportは独自のレポジトリを追加する必要があるのでひと手間かかります。
順を追って説明します。
レポジトリを追加
keyidにfigerprintを入力するところ以外は普通ですね
apt:
sources:
teleport:
keyserver: "https://deb.releases.teleport.dev/teleport-pubkey.asc"
keyid: "0c5e 8ba5 658e 320d 1b03 1179 c87e d53a 6282 c411"
source: "deb [signed-by=$KEY_FILE] https://deb.releases.teleport.dev/ stable main"
パッケージのインストール
他にもインストールしたいパッケージがあればここに追加できます。
package_update: true
package_upgrade: true
packages:
- teleport
自動起動の設定
自動起動を設定して初回起動も入れます。
runcmd:
- [ sudo, systemctl, enable, teleport.service ]
- [ sudo, systemctl, start, teleport.service ]
類似モジュールにbootcmdというのもありますが、runcmdがセットアップ時に1度だけ、処理の最後の方で実行されるのに対し、bootcmdは処理の最初の方に、毎起動時実行されるコマンドになります。
上記3つの設定を加えてterraform applyしたらteleportが立ち上がってるはずです。
configファイルの流し込み
teleport用の設定ファイルをサーバーに入れたいですよね。 ただyamlで外部ファイルをincludeできないのでyamlに直接埋め込む形にはなります… これはなんとかしたいですね。
write_files:
- path: /etc/teleport.yaml
content: |
version: v2
teleport:
nodename: 10.58.0.101
#data_dir: /persistent/teleport #これは後で使う
log:
output: stderr
severity: INFO
format:
output: text
ca_pin: []
diag_addr: ""
auth_service:
enabled: "yes"
listen_addr: 0.0.0.0:3025
proxy_listener_mode: multiplex
ssh_service:
enabled: "yes"
labels:
level: "restricted"
commands:
- name: hostname
command: [hostname]
period: 1m0s
proxy_service:
enabled: "yes"
https_keypairs: []
acme: {}
これで/etc/teleport.yamlにファイルを作成できます。
恒久ストレージを追加
今までの方法でteleportをセットアップできますが、いかんせんimmutable infrastructureって感じで設定を変更するとサーバーが再作成されちゃうので、永続性がないですよね(それが狙いではあるのですが)。
ここで、OSの入っているメインストレージの他にサブのストレージを入れて、永続化したい情報はそっちを向くようにします。
まずはmain.tfでストレージリソースを作成し、vmに接続します。
resource "esxi_virtual_disk" "proxy_disk" {
virtual_disk_disk_store = "Data" # ご自身のHDD名を入力
virtual_disk_dir = "Terraform"
virtual_disk_name = "proxy_disk.vdmk" # 名前の最後が.vdmkなのは必須
virtual_disk_size = 32
}
resource "esxi_guest" "teleport_proxy" {
...
virtual_disks {
virtual_disk_id = esxi_virtual_disk.proxy_disk.id
slot = "0:1"
}
}
でもって、cloud-initでディスクの認識・フォーマット・マウントを行います。
device_aliases:
persistent: /dev/sdb
disk_setup:
persistent:
table_type: gpt
layout: [100]
fs_setup:
- label: fs1
filesystem: ext4
device: persistent.1
mounts:
- ["persistent.1", "/persistent"]
でもってteleportのコンフィグファイルのdatadirの項目が/persistentを向くようにすれば、何度破壊されても同じ認証情報でログインできるはずです。
この状態で、tctl tokens add –type=nodeして、inviteトークンを生成してメモっておきます。(ついでにca-pinも)
子ノードとの接続(問題あり)
今までと同じ要領でリソース・cloud-initのファイルを作成してteleportの子ノードを作成します。 teleportの設定にauth_servers, auth_token, ca_pinを追記すれば自動的にクラスターに参加します。
write_files:
- path: /etc/teleport.yaml
content: |
version: v2
teleport:
nodename: *.*.*.*
data_dir: /persistent/teleport
auth_token: "********************************"
ca_pin: "sha256:****************************************************************"
auth_servers:
- *.*.*.*:3025
log:
output: stderr
severity: INFO
format:
output: text
diag_addr: ""
auth_service:
enabled: "no"
ssh_service:
enabled: "yes"
labels:
level: "normal"
commands:
- name: hostname
command: [hostname]
period: 1m0s
proxy_service:
enabled: "no"
ただinviteトークンの発行は手動でしたし、トークンにも有効期限があるのであんまりイカした方法とは呼べないですよね…
たぶんteleportにこの辺を柔軟にするAPIがないので、自分でコマンドを自動で叩いてくれるシステムを追加するか…という感じです。
記事の最初にも書きましたが見逃しちゃった人のためにもう一度… この記事ではコードの断片的な部分しか載せていないので最終形はGithubのレポジトリを参照してください。
今後
cloud-initはシンプルにまとまってて便利ではあるものの、これでサーバー全部構築できるかというとだいぶ無理がありますよね…普通にchefとか使いたい感じです。
cloud-initにはchefを呼び出す機能自体はあるのですが、ていうかchefはそもそも有料になってしまったのでCINCを使いたいですし、CINC Serverを用意して管理して…というのもちょっとめんどくさいです。
もうちょっと良げなプロビジョニング手法を探そうと思います。