zaki work log

作業ログやら生活ログやらなんやら

Ansibleからterraform applyを実行するタスクの実装

Terraformでインフラをプロビジョニングしたあとに上物に対してAnsibleで何か(アプリをインストールしたりデータを投入したり)処理をするようなことが多いので、AnsibleからTerraformを実行するようにして一発で処理できるようにするにはどうするか実装を確認してみた。

Ansibleモジュールはcommunity.general.terraformを使用する。

docs.ansible.com

applydestroyを間違えてたか所を修正(23:55)

project_pathのパス指定

/path/to/tffiles 以下でterraform applyをする構成であれば以下の通り。

  - name: apply terraform
    community.general.terraform:
      project_path: /path/to/tffiles
      state: present

このタスクを実行すると、/path/to/tffiles以下でterraform apply -auto-approveを実行するのと同様の動作になる。
TerraformのコードをAnsibleとは別リポジトリで管理してあり、AnsibleからTerraformのコードをgit cloneしてapplyするような構成であればこれで良いと思う。

一方で、構成によってはTerraformのコードをAnsibleと同じディレクトリで管理する場合もあるかと思う。その場合は実行に使うプレイブックからの相対パスproject_pathを指定する。
ただ、現時点でのcommunity.general version 8.3.0だと、ディレクトリ名だけだとパスを認識してくれないため、末尾に/を付与する必要があるっぽい。

例えば以下のディレクトリ構成でtffiles以下でterraform applyを実行する想定の場合。

.
├── inventory.ini
├── playbook.yml
└── tffiles
    ├── main.tf
    ├── outputs.tf
    ├── provider.tf
    └── variables.tf

タスクのコードとしては以下の通り。

  - name: apply terraform
    community.general.terraform:
      project_path: tffiles/
      state: present

このときにproject_pathに単にtffilesと指定すると、以下エラーとなる。

fatal: [localhost]: FAILED! => {"changed": false, "msg": "Path for Terraform project can not be None or ''."}

コード見ればわかるんだろうけど、末尾に/が無いとエラーになるのはなぜだろうね。
ちなみに./tffilesでもOK

変数指定

Terraformでvariableを使った変数定義については、community.general.terraformモジュールのvariablesパラメタで指定できる。

  - name: apply terraform
    community.general.terraform:
      project_path: ./tffiles
      state: present
      variables:
        infra_region: "{{ region }}"
        app_ami_id: "{{ app_ami }}"
        db_ami_id: "{{ db_ami }}"

この定義で、Terraform側で定義している変数infra_regionapp_ami_iddb_ami_idをAnsibleのタスク側から上書きする動作になる。

出力変数

Terraformでプロビジョニングされたリソースの情報を取得するためのoutputを使った定義については、Ansibleではタスクの実行結果の戻り値として参照できる。なので、registerディレクティブを使って後続のタスクなどで参照できる。

output "route_table_id" {
    value       = aws_route_table.example_rtb.id
    description = "ID of Route Table"
}

output "vpc_id" {
    value       = aws_vpc.example_vpc.id
    description = "ID of VPC"
}

output "nic_id" {
    value       = aws_network_interface.example_ni.id
    description = "ID of Network Interface"
}

こんなoutput定義を行っておけば、タスク定義でregisterを使って戻り値を参照でき、クラウドリソースのIDやIPアドレスを使った後続のタスクに使用できる。
以下は、AWSのルートテーブルを書き換える例。
(デフォルトで作成されるルートテーブルは作成の段階でカスタマイズできないため、作成済みのリソースをAnsibleで更新している)

  - name: apply terraform
    community.general.terraform:
      project_path: ./tffiles
      state: present
    register: result

  - name: update routetable
    amazon.aws.ec2_vpc_route_table:
      region: "{{ region }}"
      route_table_id: "{{ result.outputs.route_table_id.value }}"
      lookup: id
      vpc_id: "{{ result.outputs.vpc_id.value }}"
      routes:
        - dest: 10.1.0.0/16
          network_interface_id: "{{ result.outputs.nic_id.value }}"

環境

$ cat /etc/fedora-release 
Fedora release 39 (Thirty Nine)
$ terraform providers -version
Terraform v1.7.4
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v5.38.0
$ ansible --version
ansible [core 2.16.3]
  config file = None
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/venv/ansible9/lib64/python3.12/site-packages/ansible
  ansible collection location = /home/zaki/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/zaki/venv/ansible9/bin/ansible
  python version = 3.12.2 (main, Feb  7 2024, 00:00:00) [GCC 13.2.1 20231205 (Red Hat 13.2.1-6)] (/home/zaki/venv/ansible9/bin/python)
  jinja version = 3.1.3
  libyaml = True
$ ansible-galaxy collection list community.general

# /home/zaki/venv/ansible9/lib/python3.12/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 8.3.0  

お、今年初めてプレイブック書いた気がするぞ。

[Terraform / AWS] EventBridge Schedulerを使ったVMの自動起動と停止

EventBridge Schedulerを使ったVMの自動on/off設定をCloudFormationを使って設定するには以下のClassmethodさんの記事を見れば一通り実装できます。

dev.classmethod.jp

ここではTerraformを使った実装についてのまとめ。
こうしたらうまくいった、という感じの内容なので、コードの解説はほぼ無いです。

※ 実際に使ってるコードをコピペして変数をblog用に更新してるので、辻褄が合ってない箇所がもしあったらゴメンナサイ

EventBridgeスケジューラ用IAMロールの作成

CloudFormationだとこんな感じ(YAML)

  SchedulerEC2StopStartRole:
    # Start/Stop by EventBridge用IAMロール
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - scheduler.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: EC2StopStart
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ec2:StartInstances
                  - ec2:StopInstances
                Resource:
                  - "*"

これをTerraformで書くとこんな感じ。
使うのはaws_iam_roleリソース。

registry.terraform.io

resource "aws_iam_role" "stopstart_role" {
  name = "ec2-stop-start-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "scheduler.amazonaws.com"
        }
      },
    ]
  })

  path = "/"

  inline_policy {
    name = "start_stop_policy"
    policy = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Action   = ["ec2:StartInstances", "ec2:StopInstances"]
          Effect   = "Allow"
          Resource = "*"
        },
      ]
    })
  }
}

nameはドキュメントにはOptionalと書かれてて実際無くてもTerraform的にデプロイはできるんだけど、リソースが作られなかったので実質必須。

自動停止のスケジューラ設定

EC2を自動停止するスケジューラ設定。
CloudFormationだとこんな感じ。(YAML)
InstanceIdsに対象EC2を指定。
毎日17時25分に停止するには以下の通り。

  ScheduleEC2Stop:
    # EC2の停止EventBridge
    Type: AWS::Scheduler::Schedule
    Properties:
      Name: ec2-stop
      Description: Stop EC2 Instance
      ScheduleExpression: cron(25 17 * * ? *)
      ScheduleExpressionTimezone: Japan
      FlexibleTimeWindow:
        Mode: "OFF"
      State: ENABLED
      Target:
        Arn: arn:aws:scheduler:::aws-sdk:ec2:stopInstances
        Input: !Sub |-
          {
            "InstanceIds": ["${Server1}", "${Server2}", "${Server3}"]
          }
        RoleArn:
          Fn::GetAtt:
          - SchedulerEC2StopStartRole
          - Arn

これをTerraformで書くとこんな感じ。
使うのはaws_scheduler_scheduleリソース。

registry.terraform.io

resource "aws_scheduler_schedule" "schedule_stop" {
  name       = "ec2-stop"
  group_name = "default"

  flexible_time_window {
    mode = "OFF"
  }

  schedule_expression = "cron(25 17 * * ? *)"
  schedule_expression_timezone = "Japan"
  target {
    arn = "arn:aws:scheduler:::aws-sdk:ec2:stopInstances"
    role_arn = aws_iam_role.stopstart_role.arn
    input = jsonencode({
      InstanceIds = ["${aws_instance.server1.id}",
                     "${aws_instance.server2.id}",
                     "${aws_instance.server3.id}"]
    })
  }
}

自動起動のスケジューラ設定

最後にEC2を自動起動するスケジューラ設定。
CloudFormationだと(ry
毎朝8時45分に起動する設定。

  ScheduleEC2Start:
    # EC2の開始EventBridge
    Type: AWS::Scheduler::Schedule
    Properties:
      Name: ec2-start
      Description: Start EC2 Instance
      ScheduleExpression: cron(45 8 * * ? *)
      ScheduleExpressionTimezone: Japan
      FlexibleTimeWindow:
        Mode: "OFF"
      State: ENABLED
      Target:
        Arn: arn:aws:scheduler:::aws-sdk:ec2:startInstances
        Input: !Sub |-
          {
            "InstanceIds": ["${Server1}", "${Server2}", "${Server3}"]
          }
        RoleArn:
          Fn::GetAtt:
          - SchedulerEC2StopStartRole
          - Arn

これをTerraformで書くとこんな感じ。
使うのは停止と同様にaws_scheduler_scheduleリソース。

resource "aws_scheduler_schedule" "schedule_start" {
  name       = "ec2-start"
  group_name = "default"

  flexible_time_window {
    mode = "OFF"
  }

  schedule_expression = "cron(45 8 * * ? *)"
  schedule_expression_timezone = "Japan"
  target {
    arn = "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
    role_arn = aws_iam_role.stopstart_role.arn
    input = jsonencode({
      InstanceIds = ["${aws_instance.server1.id}",
                     "${aws_instance.server2.id}",
                     "${aws_instance.server3.id}"]
    })
  }
}

(おまけ)cron式で「平日のみ」にする場合

業務で使う場合だと、毎日起動でなく平日だけ起動にしたい場合がほとんどだと思う。
その場合はこのように書けば「月曜から金曜」になる。

cron(45 8 ? * MON-FRI *)

docs.aws.amazon.com

www.tegos.co.jp

[HashiCorp Vault] KVシークレットエンジンのデータの読み書きメモ (CLI/REST)

HashiCorp VaultのKVシークレットエンジンを使ったkey/value形式のデータの読み書きを試した。
触り始めて間もないので解釈や表現がおかしいところはあるかも。(あったら教えてください)

developer.hashicorp.com

HashiCorp VaultにおいてシークレットエンジンにはAWSAzureSSHなど様々なデータ構造の定義があり、本エントリではその中の一つであるkey/value形式のデータを扱うKVシークレットエンジンを使ったデータの読み書きについての簡単にまとめ。

ほんとはイケてないんだけど、作業はすべてrootで実施(root tokenでログイン済み状態)
RESTについては、使用するトークン(例では環境変数 VAULT_TOKEN にセットしてる)は、同様にroot tokenで、X-Vault-Tokenヘッダで指定する。X-Vault-Tokenでなく、一般的な"Authorization: Bearer $VAULT_TOKEN"でも認証可能。

KVシークレットエンジンの有効化

「KVシークレットエンジンの有効化」というと始めはピンとこなかったけど、「KV形式のデータを扱うための箱を作る」が近いイメージだと思う。
C言語風にいうとKV構造体変数を保持する領域を確保する、みたいな。

my-kv-secretという名前(パス)のKVシークレットエンジンを有効にするには以下のコマンド。

/ $ vault secrets enable -path=my-kv-secret -version=2 kv
Success! Enabled the kv secrets engine at: my-kv-secret/

-pathオプションを省略すると、kvというパスのKVシークレットエンジンが作成される。
-versionオプションを省略するとKVシークレットエンジンversion1で作成される。version2で作成する場合は-version=2を付加。
普通のLinuxコマンドと違って、オプションを末尾に付与できないので注意。

バージョンの違いについてはドキュメント参照

developer.hashicorp.com

KVシークレットエンジンの無効化

有効化の逆なので、要は削除。

/ $ vault secrets disable my-kv-secret
Success! Disabled the secrets engine (if it existed) at: my-kv-secret/

KVシークレットエンジンの一覧

/ $ vault secrets list
Path             Type         Accessor              Description
----             ----         --------              -----------
cubbyhole/       cubbyhole    cubbyhole_fa65f299    per-token private secret storage
identity/        identity     identity_0917a11b     identity store
my-kv-secret/    kv           kv_838b2e62           n/a
sys/             system       system_8a5e2f0e       system endpoints used for control, policy and debugging

-detailedを付与すると詳細が表示される。

読み書きと一覧

最初わかりづらかったんだけど、「一つのKVシークレットエンジンにkey/value形式のデータを保存」ではなく「一つのKVシークレットエンジン内にあるキー毎にkey/value形式のデータを保存」というデータ構造になってる。

という表現もやっぱりわかりづらいんで、百聞は一見に如かずで実行例。

write

CLI

my-kv-secretシークレットエンジンに、server1server2いうキーで、ユーザー名・パスワード・IPアドレスを保存。

/ $ vault kv put my-kv-secret/server1 username=zaki password=curry_tabetai host=192.168.10.89
Success! Data written to: my-kv-secret/server1
/ $ vault kv put my-kv-secret/server2 username=zaki password=curry_tabeta host=192.168.10.13
Success! Data written to: my-kv-secret/server2

ちなみにこの書式はKVシークレットエンジンversion1のもので、version2では非推奨。
version2では-mountオプションでシークレットエンジン名を指定する書式が推奨される。

/ $ vault kv put -mount=my-kv-secret server3 username=zaki password=asuha_curry host=192.168.10.10
Success! Data written to: my-kv-secret/server3

JSONファイル

JSON形式のファイルを使ったwriteも可能。

/ $ cat /var/tmp/sample.json 
{
  "username": "zaki",
  "password": "json-data",
  "host": "192.168.18.6"
}
/ $ vault kv put -mount=my-kv-secret server4 @/var/tmp/sample.json
Success! Data written to: my-kv-secret/server4

REST

developer.hashicorp.com

POSTするデータはdata以下に作成するのがポイント (ここ見落として30分くらい溶かした笑)

$ curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
  -X POST \
  -d '{ "data": { "username":"zaki", "password":"curl-post", "host":"192.168.18.21" } }' \
  http://192.168.0.75:8200/v1/my-kv-secret/data/server5
{"request_id":"45c406a2-9d38-b46c-fd48-32c887366fd8","lease_id":"","renewable":false,"lease_duration":0,"data":{"created_time":"2024-02-20T13:42:48.263373535Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":1},"wrap_info":null,"warnings":null,"auth":null}

JSONファイルを使う場合も同様

$ cat tmp/post.json 
{ "data": { "username":"zaki", "password":"curl-json-post", "host":"192.168.18.26" } }
$ curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
  -X POST \
  -d @tmp/post.json \
  http://192.168.0.75:8200/v1/my-kv-secret/data/server6
{"request_id":"3a52eeff-2be4-9b01-94cf-d6316d64f208","lease_id":"","renewable":false,"lease_duration":0,"data":{"created_time":"2024-02-20T13:45:11.476087387Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":1},"wrap_info":null,"warnings":null,"auth":null}

エンドポイントはシークレットエンジンのバージョンで異なるので注意。
前述の例はversion2の場合。
version1の場合は/v1/<secret-engine-name>/<key-name>となり、/dataが無くなる。
(先頭の/v1APIバージョンであってシークレットエンジンのバージョンではない)

developer.hashicorp.com

ここまで全部ユーザー名・パスワード・ホストと、データ構造をそろえているけど、これはたまたまそうしてるだけで、別にそろってる必要はなく柔軟に書き込み可能。

list

/ $ vault kv list my-kv-secret
Keys
----
server1
server2
server3
server4
server5
server6

writeで作った6件分のキーがリストされていることがわかる。

RESTの場合は以下の通り。

$ curl -s -H "X-Vault-Token: $VAULT_TOKEN" -X LIST http://192.168.0.75:8200/v1/my-kv-secret/metadata | python -m json.tool
{
    "request_id": "fb371bc4-b31b-7cb1-a870-e3fedc8411ef",
    "lease_id": "",
    "renewable": false,
    "lease_duration": 0,
    "data": {
        "keys": [
            "server1",
            "server2",
            "server3",
            "server4",
            "server5",
            "server6"
        ]
    },
    "wrap_info": null,
    "warnings": null,
    "auth": null
}

ちなみにAPIリファレンスにこれ乗って無さそうなんだけど。。(/metadataのあとのパス記載が全てrequiredになってる)

read

CLI

putで作成したデータは当然getで参照できる。

/ $ vault kv get my-kv-secret/server1
====== Secret Path ======
my-kv-secret/data/server1

======= Metadata =======
Key                Value
---                -----
created_time       2024-02-20T13:30:15.916047056Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
host        192.168.10.89
password    curry_tabetai
username    zaki

KVシークレットエンジンversion2を使っていると、この通りメタデータも含まれる。(version1の場合はデータのみ) 書式についてもputと同様、-mountの使用が推奨されている。

/ $ vault kv get -mount=my-kv-secret server2
====== Secret Path ======
my-kv-secret/data/server2

======= Metadata =======
Key                Value
---                -----
created_time       2024-02-20T13:30:15.990461672Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
host        192.168.10.13
password    curry_tabeta
username    zaki

また、-formatを使えばjsonyaml形式でも出力できる。

/ $ vault kv get -format=json -mount=my-kv-secret server3
{
  "request_id": "804b3606-ee34-7734-1ff3-efa9011ce5d2",
  "lease_id": "",
  "lease_duration": 0,
  "renewable": false,
  "data": {
    "data": {
      "host": "192.168.10.10",
      "password": "asuha_curry",
      "username": "zaki"
    },
    "metadata": {
      "created_time": "2024-02-20T13:30:16.03813777Z",
      "custom_metadata": null,
      "deletion_time": "",
      "destroyed": false,
      "version": 1
    }
  },
  "warnings": null
}

REST

$ curl -s -H "X-Vault-Token: $VAULT_TOKEN" http://192.168.0.75:8200/v1/my-kv-secret/data/server4 | python -m json.tool
{
    "request_id": "b6370992-2cc7-9e63-97ff-ea7daf9d0e9b",
    "lease_id": "",
    "renewable": false,
    "lease_duration": 0,
    "data": {
        "data": {
            "host": "192.168.18.6",
            "password": "json-data",
            "username": "zaki"
        },
        "metadata": {
            "created_time": "2024-02-20T13:30:16.101513188Z",
            "custom_metadata": null,
            "deletion_time": "",
            "destroyed": false,
            "version": 1
        }
    },
    "wrap_info": null,
    "warnings": null,
    "auth": null
}

環境

  • HashiCorp Vault on Kubernetes
    • CHART: vault-0.27.0
    • APP VERSION: 1.15.2

更新と削除はまた別途…


ところでSecrets EngineだったりSecrets EnginesだったりSecrets enginesだったり公式ドキュメントの表記揺れが結構あるように見える。。どれが正解なの。。

[Terraform] EC2のEIP設定とdomain="vpc"定義とAWSプロバイダーのアップグレード

EC2にEIPを設定したくて以下のようなtfファイルを作成

resource "aws_instance" "sample" {
  :
}

resource "aws_eip" "sample-eip" {
  instance   = aws_instance.sample.id
  domain     = "vpc"
  depends_on = [aws_internet_gateway.igw]

  tags = {
    Name = "sample-eip"
  }
}

するとこんなエラー。

$ terraform plan
╷
│ Error: Value for unconfigurable attribute
│ 
│   with aws_eip.sample-eip,
│   on ec2.tf line 87, in resource "aws_eip" "sample":
│   87:   domain = "vpc"
│ 
│ Can't configure a value for "domain": its value will be decided automatically based on the result of applying this configuration.
╵

ドキュメントをよく見るとdomain = "vpc"の記述は新しい書き方で、元々はvpc = trueと記述するものが廃止予定になりその替わりの記述になった模様
現に「EC2 EIP Terraform」でググってヒットする多くの情報は今日時点ではvpc = trueになっており、実際vpc = trueと書くと特にdeprecateの警告表示もなく構築可能。

なので10割の確率で使ってる環境のTerraform関連バージョンだろうと見当を付けてアップグレードを実施。

まず現在のバージョン。
そういえばかなり前に1.5にアップグレードしてその時の状態。
リリースノートを見る限り、Terraform v1.5.3が去年の7月、AWSプロバイダ v3.76.1は去年の1月リリースだったみたいね。

$ terraform -version
Terraform v1.5.3
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v3.76.1

Your version of Terraform is out of date! The latest version
is 1.7.3. You can update by downloading from https://www.terraform.io/downloads.html

terraformaptで入れてるのでapt-get upgradeで更新したので割愛。

AWSプロバイダについては、required_providers定義をまず更新。
現在の最新版が5.36.0なので、とりあえず5以上になるようにする。(雑)

--- a/terraform/provider.tf
+++ b/terraform/provider.tf
@@ -2,7 +2,7 @@ terraform {
   required_providers {
     aws = {
       source  = "hashicorp/aws"
-      version = "~> 3.0"
+      version = "~> 5.0"
     }
   }

この状態で再度init-upgradeオプションを付与して実行。

$ terraform init -upgrade

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.36.0...
- Installed hashicorp/aws v5.36.0 (signed by HashiCorp)

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

これで.terraform.lock.hclも更新される。

--- a/terraform/.terraform.lock.hcl
+++ b/terraform/.terraform.lock.hcl
@@ -2,24 +2,24 @@
 # Manual edits may be lost in future updates.
 
 provider "registry.terraform.io/hashicorp/aws" {
-  version     = "3.76.1"
-  constraints = "~> 3.0"
+  version     = "5.36.0"
+  constraints = "~> 5.0"
   hashes = [

アップグレード後のバージョンはこの通りで、EIPのdomain = "vpc"も問題なく動作する。

$ terraform providers -version
Terraform v1.7.3
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v5.36.0

この状態でEIP設定の定義をvpc = trueにしてみると、思った通り以下の警告が表示された。

╷
│ Warning: Argument is deprecated
│ 
│   with aws_eip.sample-eip,
│   on ec2.tf line 88, in resource "aws_eip" "sample-eip":
│   88:   vpc = true
│ 
│ use domain attribute instead
│ 
│ (and one more similar warning elsewhere)
╵

教訓:こまめにアップグレードしましょう。

あとそういえばEIPを自分で設定するのって初めてかも。(ポータルからも設定したことない)

Azure Key Vaultを使ったHA構成HashiCorp Vaultの自動unseal / join / remove

ここのブログでHashicorp Vaultを扱うのはそういえば初めてだったけど、Vault使う上で面倒なunsealなどを自動処理する方法を調べる機会があったのでまとめました。
なお、unseal keyやroot tokenを記事用に全部書き出してますが、機密情報なので実際は厳重に管理してください。

環境は以下の通り、HelmでK8sクラスタへデプロイしたHashicorp Vaultです。

  • HashiCorp Vault 1.15.2
    • chart version 0.27.0
  • Kubernetes: 1.29.1
    • K3s(docker compose版)
    • ホストOS: Fedora 39
    • オンプレのProxmox VEのVMに構築

あと、Azureの権限周りはあまり詳しくなくてだいぶ適当なので、不要な権限を与えてるかもしれない。詳しい人教えてください。

なお、この記事はHashicorp Vaultのデプロイ方法について書いてるだけで、アプリケーションとしてのVaultの使用方法は全く書いていません。

Helmチャートの設定

helm repo add hashicorp https://helm.releases.hashicorp.com
helm search repo hashicorp/vault
$ helm search repo hashicorp
NAME                                    CHART VERSION   APP VERSION     DESCRIPTION                                       
hashicorp/consul                        1.3.2           1.17.2          Official HashiCorp Consul Chart                   
hashicorp/terraform                     1.1.2                           Install and configure Terraform Cloud Operator ...
hashicorp/terraform-cloud-operator      2.2.0           2.2.0           Official Helm chart for HashiCorp Terraform Clo...
hashicorp/terraform-enterprise          1.1.1           1.16.0          Official HashiCorp Terraform-Enterprise Chart     
hashicorp/vault                         0.27.0          1.15.2          Official HashiCorp Vault Chart                    
hashicorp/vault-secrets-operator        0.4.3           0.4.3           Official Vault Secrets Operator Chart             
hashicorp/waypoint                      0.1.21          0.11.3          Official Helm Chart for HashiCorp Waypoint       

スタンドアロンモードのインストール(前置き)

読み飛ばし可

デプロイ

デフォルト値でVaultをインストール。(values.yamlは不要)

$ helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace
Release "my-vault" does not exist. Installing it now.
NAME: my-vault
LAST DEPLOYED: Sat Feb 10 16:01:21 2024
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!

Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:

https://developer.hashicorp.com/vault/docs


Your release is named my-vault. To learn more about the release, try:

  $ helm status my-vault
  $ helm get manifest my-vault

StatefulSetでVaultのpodがデプロイされますがnot readyになります。

$ kubectl get pod,sts -n vault 
NAME                                           READY   STATUS    RESTARTS   AGE
pod/my-vault-agent-injector-6b66d76649-xmvbc   1/1     Running   0          25s
pod/my-vault-0                                 0/1     Running   0          25s

NAME                        READY   AGE
statefulset.apps/my-vault   0/1     25s

ログを見ると以下の通り。

2024-02-10T07:01:56.144Z [INFO]  core: security barrier not initialized
2024-02-10T07:01:56.144Z [INFO]  core: seal configuration missing, not initialized

Vaultのステータスをvault statusコマンドを実行して確認すると、未初期化(Initializedがtrue)であることが確認できます。

$ kubectl exec -n vault my-vault-0 -- vault status
Key                Value
---                -----
Seal Type          shamir
Initialized        false
Sealed             true
Total Shares       0
Threshold          0
Unseal Progress    0/0
Unseal Nonce       n/a
Version            1.15.2
Build Date         2023-11-06T11:33:28Z
Storage Type       file
HA Enabled         false

初期化とunseal

Vaultを使える状態にするにはunsealが必要で、そのためにはまず初期化します。
初期化はVaultのコマンドvault operator initを実行します。

$ kubectl exec -n vault my-vault-0 -- vault operator init
Unseal Key 1: PZuSm+GpsG6xoLz/1hm+kdke/q/UWm+kx2ewtyp1yJ3x
Unseal Key 2: 2eW+IlscQ1cKEu/pATh3ZJvJ5EZ9D5jopRbrUgACiC8h
Unseal Key 3: DbVJzW0vcT4a4bhS3lNIihwTizYridY6Rzt282Prl34m
Unseal Key 4: Ba4+tWfLXtyQ0hXvdHQbXlOOfHqCPvTgDDC7wtN6Ok86
Unseal Key 5: avg1gIAbhDDk4SRpSqby6gDENhOJPJzqk93CE8yCar+c

Initial Root Token: hvs.jnkpJnAEN4PDyPZZF3RJoGJV

Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.

Vault does not store the generated root key. Without at least 3 keys to
reconstruct the root key, Vault will remain permanently sealed!

It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.

これでrootトークンと5つのunsealキーを取得できました。
ステータスを確認すると、Initializedがtrueになります。
※ これは記事用に全部書き出してますが、機密情報なので実際は厳重に管理してください。

$ kubectl exec -n vault my-vault-0 -- vault status
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    0/3
Unseal Nonce       n/a
Version            1.15.2
Build Date         2023-11-06T11:33:28Z
Storage Type       file
HA Enabled         false

初期化が済んだらunsealを行います。
unsealはvault operator unsealを実行します。
ここでは記事用に引数に指定してますが、機密情報をローカルエコーするのはよくないので、引数無しで実行して対話的に入力した方がよいです。(が、自動化するならこの方法かな?)

$ kubectl exec -n vault my-vault-0 -- vault operator unseal PZuSm+GpsG6xoLz/1hm+kdke/q/UWm+kx2ewtyp1yJ3x
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    1/3
Unseal Nonce       b8ff2c62-5fdd-18b0-6e20-ddff956f7b2f
Version            1.15.2
Build Date         2023-11-06T11:33:28Z
Storage Type       file
HA Enabled         false

Unseal Progressが1/3になりました。あと2回実行する必要があるので、5つあるキーのうちあと2個使って同じコマンドを実行します。

$ kubectl exec -n vault my-vault-0 -- vault operator unseal 2eW+IlscQ1cKEu/pATh3ZJvJ5EZ9D5jopRbrUgACiC8h
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    2/3
Unseal Nonce       b8ff2c62-5fdd-18b0-6e20-ddff956f7b2f
Version            1.15.2
Build Date         2023-11-06T11:33:28Z
Storage Type       file
HA Enabled         false
$ kubectl exec -n vault my-vault-0 -- vault operator unseal DbVJzW0vcT4a4bhS3lNIihwTizYridY6Rzt282Prl34m
Key             Value
---             -----
Seal Type       shamir
Initialized     true
Sealed          false
Total Shares    5
Threshold       3
Version         1.15.2
Build Date      2023-11-06T11:33:28Z
Storage Type    file
Cluster Name    vault-cluster-2f48696e
Cluster ID      e109d3d3-5d52-c081-667f-8fefcc8067f6
HA Enabled      false

これでSealedの値がfalseになり、unseal状態になりました。

$ kubectl get pod -n vault 
NAME                                       READY   STATUS    RESTARTS   AGE
my-vault-agent-injector-6b66d76649-xmvbc   1/1     Running   0          4m47s
my-vault-0                                 1/1     Running   0          4m47s

この通りunsealされるとPodはready状態になり、利用準備ができました。

HAクラスターでのインストール

以下は、スタンドアロンでデプロイした環境はすべて削除済みで新規に構築しています。

ここからは耐障害性を高めるためのHAクラスタ構成でデプロイします。
※ AntiAffinityの設定によって1pod1nodeデプロイになるため、あらかじめノードは5ノードでKubernetesを構築しています。

$ kubectl get node
NAME           STATUS   ROLES                  AGE     VERSION
b513c8231aad   Ready    <none>                 6h41m   v1.29.1+k3s2
9952de1bc166   Ready    <none>                 6h41m   v1.29.1+k3s2
2f5893c02aa3   Ready    <none>                 6h40m   v1.29.1+k3s2
412b213f3d10   Ready    <none>                 6h41m   v1.29.1+k3s2
4c1f8de42bfe   Ready    control-plane,master   6h41m   v1.29.1+k3s2
8239e04d52ef   Ready    <none>                 6h41m   v1.29.1+k3s2

values.yamlは以下の通り。
configの部分はデフォルトのままですが、本題で使うので含めています。
setNodeIdは無くても良いですが、Vaultノード名が分かりやすくなるので有効にしています。

server:
  # Run Vault in "HA" mode. There are no storage requirements unless the audit log
  # persistence is required.  In HA mode Vault will configure itself to use Consul
  # for its storage backend.  The default configuration provided will work the Consul
  # Helm project by default.  It is possible to manually configure Vault to use a
  # different HA backend.
  ha:
    enabled: true
    replicas: 1


    # Enables Vault's integrated Raft storage.  Unlike the typical HA modes where
    # Vault's persistence is external (such as Consul), enabling Raft mode will create
    # persistent volumes for Vault to store data according to the configuration under server.dataStorage.
    # The Vault cluster will coordinate leader elections and failovers internally.
    raft:

      # Enables Raft integrated storage
      enabled: true
      # Set the Node Raft ID to the name of the pod
      setNodeId: true

      # Note: Configuration files are stored in ConfigMaps so sensitive data
      # such as passwords should be either mounted through extraSecretEnvironmentVars
      # or through a Kube secret.  For more information see:
      # https://developer.hashicorp.com/vault/docs/platform/k8s/helm/run#protecting-sensitive-vault-configurations
      config: |
        ui = true

        listener "tcp" {
          tls_disable = 1
          address = "[::]:8200"
          cluster_address = "[::]:8201"
          # Enable unauthenticated metrics access (necessary for Prometheus Operator)
          #telemetry {
          #  unauthenticated_metrics_access = "true"
          #}
        }

        storage "raft" {
          path = "/vault/data"
        }

        service_registration "kubernetes" {}
helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace -f values.yaml 
$ kubectl get pod -n vault -o wide
NAME                                       READY   STATUS    RESTARTS   AGE     IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-0                                 0/1     Running   0          5m47s   10.42.4.13   9952de1bc166   <none>           <none>
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          5m47s   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-1                                 0/1     Running   0          5m46s   10.42.3.5    8239e04d52ef   <none>           <none>
my-vault-2                                 0/1     Running   0          5m46s   10.42.2.5    4c1f8de42bfe   <none>           <none>

3podがデプロイされましたが、スタンドアロンの場合同様に、初期化とunsealが必要です。

リーダーノードの初期化とunseal

レプリカ3のStatefulSetなので、0番のpodでまず初期化とunsealを実施。

$ kubectl exec -n vault my-vault-0 -- vault operator init
Unseal Key 1: ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
Unseal Key 2: ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
Unseal Key 3: pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
Unseal Key 4: 6q2Fi8q7AOc0n6iPJeoWTnio6QjCDMlxwZFyD7uw9GqE
Unseal Key 5: zLEVdCLOMXAptoZmBZRYVpSG8o8lCfK/bJi5pQ7jE8Sr

Initial Root Token: hvs.GuR8WbGjN7X3roX6UoJYztON

Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.

Vault does not store the generated root key. Without at least 3 keys to
reconstruct the root key, Vault will remain permanently sealed!

It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
kubectl exec -n vault my-vault-0 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-0 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-0 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM

これでvault-0がunsealされreadyになりました。

$ kubectl get pod -n vault -o wide
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          11m   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-1                                 0/1     Running   0          11m   10.42.3.5    8239e04d52ef   <none>           <none>
my-vault-2                                 0/1     Running   0          11m   10.42.2.5    4c1f8de42bfe   <none>           <none>
my-vault-0                                 1/1     Running   0          11m   10.42.4.13   9952de1bc166   <none>           <none>

ノードのクラスターへの参加

現時点ではまだシングルノード状態。

/ $ vault login hvs.GuR8WbGjN7X3roX6UoJYztON
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                hvs.GuR8WbGjN7X3roX6UoJYztON
token_accessor       GpmzT1ZyBYs1bduOTRYVnALW
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]
/ $ vault operator members
Host Name     API Address               Cluster Address                              Active Node    Version    Upgrade Version    Redundancy Zone    Last Echo
---------     -----------               ---------------                              -----------    -------    ---------------    ---------------    ---------
my-vault-0    http://10.42.4.13:8200    https://my-vault-0.my-vault-internal:8201    true           1.15.2     1.15.2             n/a                n/a

HA構成にするため残りの2podをクラスターに参加させるには、それぞれraft join操作をリーダーであるvault-0に対して行います。
podではHTTPでlistenしているので、http://を付与するのをお忘れなく。
URL(pod名service名)はHelmのリリース名に依存するのでそこは読み替えること。

$ kubectl exec -n vault my-vault-1 -- vault operator raft join http://my-vault-0.my-vault-internal:8200
Key       Value
---       -----
Joined    true
$ kubectl exec -n vault my-vault-2 -- vault operator raft join http://my-vault-0.my-vault-internal:8200
Key       Value
---       -----
Joined    true
$ kubectl get pod -n vault -o wide
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          33m   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-1                                 0/1     Running   0          33m   10.42.3.5    8239e04d52ef   <none>           <none>
my-vault-2                                 0/1     Running   0          33m   10.42.2.5    4c1f8de42bfe   <none>           <none>
my-vault-0                                 1/1     Running   0          33m   10.42.4.13   9952de1bc166   <none>           <none>

これでvault-1およびvault-2は初期化済みseal状態になるので、それぞれunsealを行います。

kubectl exec -n vault my-vault-1 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-1 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-1 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
kubectl exec -n vault my-vault-2 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-2 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-2 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM

これでようやく全podがready状態になり、クラスターに参加できた状態になりました。

$ kubectl get pod -n vault -o wide
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          35m   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-0                                 1/1     Running   0          35m   10.42.4.13   9952de1bc166   <none>           <none>
my-vault-1                                 1/1     Running   0          35m   10.42.3.5    8239e04d52ef   <none>           <none>
my-vault-2                                 1/1     Running   0          35m   10.42.2.5    4c1f8de42bfe   <none>           <none>

$ kubectl exec -it -n vault my-vault-0 -- vault operator members
Host Name     API Address               Cluster Address                              Active Node    Version    Upgrade Version    Redundancy Zone    Last Echo
---------     -----------               ---------------                              -----------    -------    ---------------    ---------------    ---------
my-vault-2    http://10.42.2.5:8200     https://my-vault-2.my-vault-internal:8201    false          1.15.2     1.15.2             n/a                2024-02-10T12:30:09Z
my-vault-1    http://10.42.3.5:8200     https://my-vault-1.my-vault-internal:8201    false          1.15.2     1.15.2             n/a                2024-02-10T12:30:07Z
my-vault-0    http://10.42.4.13:8200    https://my-vault-0.my-vault-internal:8201    true           1.15.2     1.15.2             n/a                n/a

これでHA構成でのVaultの利用準備ができました。

スケールアウト/スケールイン

values.yamlreplicas: 3を指定したため、初期状態で3つのpodがデプロイされていますが、StatefulSetのレプリカ数を変更することでスケールできます。
例えば5台構成にするには、普通にkubectl scaleで設定できます。

$ kubectl get sts -n vault 
NAME       READY   AGE
my-vault   3/3     11h
$ kubectl scale -n vault statefulset --replicas 5 my-vault 
statefulset.apps/my-vault scaled

$ kubectl get pod -n vault -o wide 
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          11h   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-0                                 1/1     Running   0          11h   10.42.4.13   9952de1bc166   <none>           <none>
my-vault-1                                 1/1     Running   0          11h   10.42.3.5    8239e04d52ef   <none>           <none>
my-vault-2                                 1/1     Running   0          11h   10.42.2.5    4c1f8de42bfe   <none>           <none>
my-vault-3                                 0/1     Running   0          34s   10.42.0.5    2f5893c02aa3   <none>           <none>
my-vault-4                                 0/1     Running   0          34s   10.42.1.5    412b213f3d10   <none>           <none>

ただし、新しく作成されたPodはやはりraft joinunsealが必要です。

kubectl exec -n vault my-vault-3 -- vault operator raft join http://my-vault-0.my-vault-internal:8200
kubectl exec -n vault my-vault-4 -- vault operator raft join http://my-vault-0.my-vault-internal:8200

kubectl exec -n vault my-vault-3 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-3 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-3 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
kubectl exec -n vault my-vault-4 -- vault operator unseal ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
kubectl exec -n vault my-vault-4 -- vault operator unseal ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
kubectl exec -n vault my-vault-4 -- vault operator unseal pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM

これで5台クラスターとなりました。

$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node          Address                              State       Voter
----          -------                              -----       -----
my-vault-0    my-vault-0.my-vault-internal:8201    leader      true
my-vault-1    my-vault-1.my-vault-internal:8201    follower    true
my-vault-2    my-vault-2.my-vault-internal:8201    follower    true
my-vault-3    my-vault-3.my-vault-internal:8201    follower    true
my-vault-4    my-vault-4.my-vault-internal:8201    follower    true

スケールインの場合は、いきなりレプリカ数を減らすのはNGで、まずVault的にクラスターから除外する必要があり、joinと逆でremove-peerを行います。
ここでは追加でスケールしたvault-3とvault-4を削除し、レプリカ3に戻す作業を行ってみます。
といってもコマンドは簡単で、remove-peerの引数にlist-peersでリストされているNode名を指定します。
この例ではvalues.yamlsetNodeId: trueの設定を入れているのでNode名=Pod名になってます。(指定がオフの場合はランダムなハッシュ値になります)

$ kubectl exec -n vault my-vault-0 -- vault operator raft remove-peer my-vault-4
Peer removed successfully!
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node          Address                              State       Voter
----          -------                              -----       -----
my-vault-0    my-vault-0.my-vault-internal:8201    leader      true
my-vault-1    my-vault-1.my-vault-internal:8201    follower    true
my-vault-2    my-vault-2.my-vault-internal:8201    follower    true
my-vault-3    my-vault-3.my-vault-internal:8201    follower    true

これでmy-vault-4クラスターから除外されました。
同じ要領でmy-vault-3も除外します。

$ kubectl exec -n vault my-vault-0 -- vault operator raft remove-peer my-vault-3
Peer removed successfully!
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node          Address                              State       Voter
----          -------                              -----       -----
my-vault-0    my-vault-0.my-vault-internal:8201    leader      true
my-vault-1    my-vault-1.my-vault-internal:8201    follower    true
my-vault-2    my-vault-2.my-vault-internal:8201    follower    true

これでStatefulSetのレプリカ数を3に戻せばOKです。

$ kubectl scale -n vault statefulset --replicas 3 my-vault 
statefulset.apps/my-vault scaled
$ kubectl get pod -n vault -o wide 
NAME                                       READY   STATUS        RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running       0          11h   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-0                                 1/1     Running       0          11h   10.42.4.13   9952de1bc166   <none>           <none>
my-vault-1                                 1/1     Running       0          11h   10.42.3.5    8239e04d52ef   <none>           <none>
my-vault-2                                 1/1     Running       0          11h   10.42.2.5    4c1f8de42bfe   <none>           <none>
my-vault-4                                 1/1     Terminating   0          19m   10.42.1.5    412b213f3d10   <none>           <none>
my-vault-3                                 1/1     Terminating   0          19m   10.42.0.5    2f5893c02aa3   <none>           <none>

remove-peerを行わずにスケールインを行うと、削除された分のPodはクラスターには残ったままunhealth状態で残ったままとなります。
いきなりレプリカ3にすると、以下のようになります。

peersのリストは5台ある状態。

$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node          Address                              State       Voter
----          -------                              -----       -----
my-vault-0    my-vault-0.my-vault-internal:8201    leader      true
my-vault-1    my-vault-1.my-vault-internal:8201    follower    true
my-vault-2    my-vault-2.my-vault-internal:8201    follower    true
my-vault-3    my-vault-3.my-vault-internal:8201    follower    true
my-vault-4    my-vault-4.my-vault-internal:8201    follower    true

ただし、ステータスはhealthがfalseになります。

$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot state
Healthy:                         false
Failure Tolerance:               0
Leader:                          my-vault-0
Voters:
   my-vault-0
   my-vault-1
   my-vault-2
   my-vault-3
   my-vault-4
Servers:
   my-vault-0
   :
   :
   my-vault-3
      Name:              my-vault-3
      Address:           my-vault-3.my-vault-internal:8201
      Status:            voter
      Node Status:       alive
      Healthy:           false
   :
   :
   my-vault-4
      Name:              my-vault-4
      Address:           my-vault-4.my-vault-internal:8201
      Status:            voter
      Node Status:       alive
      Healthy:           false

この状態からでもremove-peer my-vault-3 remove-peer my-vault-4を実行すれば3台構成へ変更可能。

自動unsealと自動join (ここから本題)

ここまで手動での初期化とunsealを行ったデプロイについてみてきましたが…
Pod起動時に必ずunsealが必要なため、率直に言ってKubernetesのオートヒーリングなどとの相性も悪く、運用でこのコマンド実行はやってられないと思います。
例えばkubectl delete podして再作成されたpodもnot readyなので、都度unsealする必要があります。

最初はこの仕様を知ってnot readyなpodを検出してunsealを行うプログラムとか書いてやろうと思いましたが、そんなことしなくても標準でクラウドサービスの機密情報を保持する機能と連携してauto unsealを行う機能が備わっています。
本エントリでは、AzureのKey Vaultサービスを使ったauto unsealについて説明します。

※ ここまでは単にVaultと雑に記載してましたが、ここからはAzure Key Vaultとややこしくなるので、「HashiCorp Vault」「Azure Key Vault」と表記します。

auto unseal with Azure Key Vault

公式ドキュメントにチュートリアルがあります。

developer.hashicorp.com

チュートリアルの流れをざっくり紹介すると、以下の通り

  1. TerraformでAzureにVMやAzure Key Vaultなどのリソース作成
  2. VM上にAzure Key Vault連携するHashiCorp Vaultのデプロイ

なので仕組みを把握するにはこの中身を追っていけばOK
言い方を変えると「自分の環境のVaultで自動unsealをするには」を実現するには中身を読み解く必要がありました。

アプリケーションとシークレットの作成

アプリの登録」で、「新規登録」から新しくAuto-unseal用のトークン発行のためのアプリを登録します。

作成できたら、概要ページで以下の値をメモ

  • アプリケーション(クライアント)ID
  • ディレクトリ(テナント)ID

次に「証明書とシークレット」で「クライアント シークレット」を新規作成。

今回は動作確認用なので、デフォルトの180日期限。

作成できたらシークレットの値(IDでなく)をメモ(一度画面を離れると非表示になるので注意)。

次に「APIのアクセス許可」で「アクセス許可の追加」し、公式ドキュメントは「Azure Active Directory Graph」を追加するよう記載されてるけど2022年6月に非推奨になったようなので、かわりに「Microsoft Graph」を選択。

まず「委任されたアクセス許可」を押下し、「User」の項目にある「User.Read」にチェックされているのを確認(デフォルトでこれがチェックされてる)。

次に「アプリケーションの許可」を押下し、以下をチェック

  • 「Application」の項目にある「Application.ReadWrite.All」
  • 「Directory」の項目にある「Directory.ReadWrite.All」

最後に下部の「アクセス許可の追加」を押下。
APIのアクセス許可の画面に戻ると、状態が「既定のディレクトリに付与されていません」になっているので、「既定のディレクトリに管理者の同意を与えます」を押下。

「既定のディレクトリに付与されました」になればOK

次にサブスクリプションの画面で、まずサブスクリプションIDを確認。

次に「アクセス制御(IAM)」の画面で「+追加」から「ロール割り当ての追加」を押下。

「ロール」タブでOwnerを選択とあるけど……おそらく「特権管理者ロール」で「所有者」で検索して出てくるものを選択。ちょっと権限強すぎそうだけど、とりあえず。

次に「メンバー」タブを押下し、「アクセスの割り当先」に「ユーザー、グループ、またはサービスプリンシパル」をチェック、「メンバーを選択する」で、先ほど作成したアプリケーション名を入力。

条件はドキュメントには何も書かれてない(「所有者」を選択するとこれが必要)けど、デフォルトのまま進めると割り当てできないので、適当だけど「ロールとプリンシパルの選択」から「ロールの制約」->「ロールの追加」で「所有者」をチェック。これで「レビューと割り当て」が可能になります。

キーコンテナーの作成と権限付与

ここからはチュートリアルのドキュメントでなく、資材として用意されてるTerraformのコードを参考にします。

github.com

まずはキーコンテナーを作成します。

アクセス構成の画面では、「自分」に操作権限を付与するために、「アクセス許可モデル」を「コンテナーのアクセスポリシー」にチェックし、下部に追加される「アクセスポリシー」で自分をチェック。

ネットワークはデフォルトのままで作成します。

作成が完了したら、再度作成済みのキーコンテナーに移動し、「アクセスポリシー」画面で「作成」し、「キーのアクセス許可」で以下3つにチェックを入れます。

  • 取得
  • キーの折り返しを解除
  • キーを折り返す

プリンシパルでは作成済みアプリケーションのIDか名前を入力して選択、あとはそのまま作成します。

次に「キー」に移動し、unsealに使うキーを作成します。
パラメタはデフォルトのRSA2048ビットのままでOKです。

TerraformのコードにはキーのオプションでwrapKey/unwrapKeyがありますが、作成時に選択できる画面がないのでそのまま作成します。
作成後に詳細を見ると必要な項目はチェックがついてるのでそのまま使用します。

これでAzure Key Vaultの名前と、キーの名前がそろいました。

HashiCorp Vault設定

Azure Key Vaultを使った設定はこちら。

developer.hashicorp.com

auto unseal設定

取得できた各値をセットして再デプロイします。
自動join設定はまだ入れてないので、いったんレプリカ数は1にし、消えたPodが使っていたストレージ(pvc/pv)も削除しておきます。

server:
  # Run Vault in "HA" mode. There are no storage requirements unless the audit log
  # persistence is required.  In HA mode Vault will configure itself to use Consul
  # for its storage backend.  The default configuration provided will work the Consul
  # Helm project by default.  It is possible to manually configure Vault to use a
  # different HA backend.
  ha:
    enabled: true
    replicas: 1


    # Enables Vault's integrated Raft storage.  Unlike the typical HA modes where
    # Vault's persistence is external (such as Consul), enabling Raft mode will create
    # persistent volumes for Vault to store data according to the configuration under server.dataStorage.
    # The Vault cluster will coordinate leader elections and failovers internally.
    raft:

      # Enables Raft integrated storage
      enabled: true
      # Set the Node Raft ID to the name of the pod
      setNodeId: true

      # Note: Configuration files are stored in ConfigMaps so sensitive data
      # such as passwords should be either mounted through extraSecretEnvironmentVars
      # or through a Kube secret.  For more information see:
      # https://developer.hashicorp.com/vault/docs/platform/k8s/helm/run#protecting-sensitive-vault-configurations
      config: |
        ui = true

        listener "tcp" {
          tls_disable = 1
          address = "[::]:8200"
          cluster_address = "[::]:8201"
          # Enable unauthenticated metrics access (necessary for Prometheus Operator)
          #telemetry {
          #  unauthenticated_metrics_access = "true"
          #}
        }

        storage "raft" {
          path = "/vault/data"
        }

        service_registration "kubernetes" {}

        seal "azurekeyvault" {
          client_id      = "YOUR-APP-ID"
          client_secret  = "YOUR-APP-PASSWORD"
          tenant_id      = "YOUR-AZURE-TENANT-ID"
          vault_name     = "Azure Key Value name on Azure"
          key_name       = "Key name on Azure"
        }
helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace -f values.yaml 

ただし、設定のConfigMapは更新されるけど、Podは再作成されず設定が反映されないのでPodを削除します。

$ kubectl delete pod -n vault my-vault-0 
pod "my-vault-0" deleted
$ kubectl get pod -n vault -o wide 
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          17h   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-0                                 0/1     Running   0          3s    10.42.4.18   9952de1bc166   <none>           <none>

起動しましたが、まだAuto-unseal設定が反映されていないためnot readyになります。ここで-migrateオプションをつけてunsealを行います。

$ kubectl exec -n vault my-vault-0 -- vault operator unseal -migrate ZQOZnaKVQBx8ppKkkChuIZ0NHRDbeyb0WaK1FALh4lB2
Key                           Value
---                           -----
Recovery Seal Type            shamir
Initialized                   true
Sealed                        true
Total Recovery Shares         5
Threshold                     3
Unseal Progress               1/3
...

$ kubectl exec -n vault my-vault-0 -- vault operator unseal -migrate ddRbGF6oZ9K1ccy8anz/d/pY5CxFAoxHNL/5HDH9C3hz
Key                           Value
---                           -----
Recovery Seal Type            shamir
Initialized                   true
Sealed                        true
Total Recovery Shares         5
Threshold                     3
Unseal Progress               2/3
...

$ kubectl exec -n vault my-vault-0 -- vault operator unseal -migrate pcwmjTj4HxJxFRS8h/9THcygRUV0BUGo1qWFuAydNJoM
Key                           Value
---                           -----
Recovery Seal Type            shamir
Initialized                   true
Sealed                        false
Total Recovery Shares         5
Threshold                     3
Seal Migration in Progress    true

$ kubectl get pod -n vault -o wide
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          17h   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-0                                 1/1     Running   0          49s   10.42.4.22   9952de1bc166   <none>           <none>

これでunsealとともにAuto-unsealも有効になります。

$ kubectl delete pod -n vault my-vault-0 
pod "my-vault-0" deleted

ためしにPodを削除してみると、

$ kubectl get pod -n vault -o wide -w
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          17h   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-0                                 0/1     Running   0          1s    10.42.4.25   9952de1bc166   <none>           <none>
my-vault-0                                 1/1     Running   0          5s    10.42.4.25   9952de1bc166   <none>           <none>

この通り、自動でunsealされready状態になります。

auto join設定

この時点で自動unsealは動作しますが、レプリカ数を増やした時のクラスターへのjoinは別途必要です。
が、これも自動で処理するための設定があり、retry_joinでリーダーのサーバーを指定しておけば良いです。

developer.hashicorp.com

      config: |
        ui = true

        listener "tcp" {
          tls_disable = 1
          address = "[::]:8200"
          cluster_address = "[::]:8201"
          # Enable unauthenticated metrics access (necessary for Prometheus Operator)
          #telemetry {
          #  unauthenticated_metrics_access = "true"
          #}
        }

        storage "raft" {
          path = "/vault/data"

          retry_join {
            leader_api_addr = "http://my-vault-0.my-vault-internal:8200"
          }
          retry_join {
            leader_api_addr = "http://my-vault-1.my-vault-internal:8200"
          }
          retry_join {
            leader_api_addr = "http://my-vault-2.my-vault-internal:8200"
          }
        }

        service_registration "kubernetes" {}

        seal "azurekeyvault" {
          :
        }

これでレプリカを3にすると、

$ kubectl scale -n vault statefulset --replicas 3 my-vault 
statefulset.apps/my-vault scaled
$ kubectl get pod -n vault -o wide -w
NAME                                       READY   STATUS    RESTARTS   AGE    IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          18h    10.42.5.12   b513c8231aad   <none>           <none>
my-vault-0                                 1/1     Running   0          2m9s   10.42.4.25   9952de1bc166   <none>           <none>
my-vault-1                                 0/1     Pending   0          2s     <none>       <none>         <none>           <none>
my-vault-2                                 0/1     Pending   0          2s     <none>       <none>         <none>           <none>
my-vault-1                                 0/1     Pending   0          3s     <none>       8239e04d52ef   <none>           <none>
my-vault-1                                 0/1     ContainerCreating   0          3s     <none>       8239e04d52ef   <none>           <none>
my-vault-1                                 0/1     Running             0          4s     10.42.3.10   8239e04d52ef   <none>           <none>
my-vault-1                                 0/1     Running             0          4s     10.42.3.10   8239e04d52ef   <none>           <none>
my-vault-1                                 0/1     Running             0          9s     10.42.3.10   8239e04d52ef   <none>           <none>
my-vault-1                                 0/1     Running             0          9s     10.42.3.10   8239e04d52ef   <none>           <none>
my-vault-1                                 1/1     Running             0          9s     10.42.3.10   8239e04d52ef   <none>           <none>
my-vault-2                                 0/1     Pending             0          9s     <none>       b513c8231aad   <none>           <none>
my-vault-2                                 0/1     ContainerCreating   0          9s     <none>       b513c8231aad   <none>           <none>
my-vault-2                                 0/1     ContainerCreating   0          17s    <none>       b513c8231aad   <none>           <none>
my-vault-2                                 0/1     Running             0          18s    10.42.5.14   b513c8231aad   <none>           <none>
my-vault-2                                 0/1     Running             0          28s    10.42.5.14   b513c8231aad   <none>           <none>
my-vault-2                                 0/1     Running             0          28s    10.42.5.14   b513c8231aad   <none>           <none>
my-vault-2                                 1/1     Running             0          28s    10.42.5.14   b513c8231aad   <none>           <none>

$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node          Address                              State       Voter
----          -------                              -----       -----
my-vault-0    my-vault-0.my-vault-internal:8201    leader      true
my-vault-1    my-vault-1.my-vault-internal:8201    follower    true
my-vault-2    my-vault-2.my-vault-internal:8201    follower    true

この通り、自動でクラスターへ追加とunsealが行われるようになりました。

auto remove設定

この状態ならスケーリングの際の運用上の支障はほぼなくなったと思いますが、ついでなのでスケールインの際のクラスターからの除外も自動化してみます。
これはraftのautopilotを使います。

$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot state
Healthy:                         true
Failure Tolerance:               1
Leader:                          my-vault-0
Voters:
   my-vault-0
   my-vault-1
   my-vault-2
Servers:
   my-vault-0
   :
   :

現在3台構成のため、耐障害性は1(1台だけならダウンしてもサービス可能)の状態。

ここにautopilotの設定を追加していきます。(ただ、コマンドかAPIで入れるしか例がないんだけど、設定ファイルでは不可?)

現在の設定

$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot get-config
Key                                   Value
---                                   -----
Cleanup Dead Servers                  false
Last Contact Threshold                10s
Dead Server Last Contact Threshold    24h0m0s
Server Stabilization Time             10s
Min Quorum                            0
Max Trailing Logs                     1000
Disable Upgrade Migration             false

ここに「停止サーバーの削除判定閾値を1分」「停止サーバーを自動削除」「最小台数を3」「正常状態とみなすまでの時間」をセットします。

$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot set-config \
  -dead-server-last-contact-threshold=1m \
  -cleanup-dead-servers=true \
  -min-quorum=3 \
  -server-stabilization-time=60

$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot get-config
Key                                   Value
---                                   -----
Cleanup Dead Servers                  true
Last Contact Threshold                10s
Dead Server Last Contact Threshold    1m0s
Server Stabilization Time             1m0s
Min Quorum                            3
Max Trailing Logs                     1000
Disable Upgrade Migration             false

ではまずレプリカを5にセット。

$ kubectl scale -n vault statefulset --replicas 5 my-vault 
statefulset.apps/my-vault scaled

$ kubectl get pod -n vault -o wide 
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running   0          18h   10.42.5.12   b513c8231aad   <none>           <none>
my-vault-0                                 1/1     Running   0          28m   10.42.4.25   9952de1bc166   <none>           <none>
my-vault-1                                 1/1     Running   0          26m   10.42.3.10   8239e04d52ef   <none>           <none>
my-vault-2                                 1/1     Running   0          26m   10.42.5.14   b513c8231aad   <none>           <none>
my-vault-3                                 1/1     Running   0          33s   10.42.1.12   412b213f3d10   <none>           <none>
my-vault-4                                 1/1     Running   0          33s   10.42.2.14   4c1f8de42bfe   <none>           <none>

$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node          Address                              State       Voter
----          -------                              -----       -----
my-vault-0    my-vault-0.my-vault-internal:8201    leader      true
my-vault-1    my-vault-1.my-vault-internal:8201    follower    true
my-vault-2    my-vault-2.my-vault-internal:8201    follower    true
my-vault-3    my-vault-3.my-vault-internal:8201    follower    true
my-vault-4    my-vault-4.my-vault-internal:8201    follower    true

$ kubectl exec -n vault my-vault-0 -- vault operator raft autopilot state
Healthy:                         true
Failure Tolerance:               2
Leader:                          my-vault-0
Voters:
   my-vault-0
   my-vault-1
   my-vault-2
   my-vault-3
   my-vault-4

5台構成(耐障害は2台まで)になったので、次は手動のクラスターから除外せずにレプリカを3に設定

$ kubectl get pod -n vault -o wide 
NAME                                       READY   STATUS        RESTARTS   AGE    IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-9284p   1/1     Running       0          18h    10.42.5.12   b513c8231aad   <none>           <none>
my-vault-0                                 1/1     Running       0          30m    10.42.4.25   9952de1bc166   <none>           <none>
my-vault-1                                 1/1     Running       0          28m    10.42.3.10   8239e04d52ef   <none>           <none>
my-vault-2                                 1/1     Running       0          28m    10.42.5.14   b513c8231aad   <none>           <none>
my-vault-4                                 1/1     Terminating   0          3m5s   10.42.2.14   4c1f8de42bfe   <none>           <none>
my-vault-3                                 1/1     Terminating   0          3m5s   10.42.1.12   412b213f3d10   <none>           <none>
$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node          Address                              State       Voter
----          -------                              -----       -----
my-vault-0    my-vault-0.my-vault-internal:8201    leader      true
my-vault-1    my-vault-1.my-vault-internal:8201    follower    true
my-vault-2    my-vault-2.my-vault-internal:8201    follower    true
my-vault-3    my-vault-3.my-vault-internal:8201    follower    true
my-vault-4    my-vault-4.my-vault-internal:8201    follower    true

# 約60秒後

$ kubectl exec -n vault my-vault-0 -- vault operator raft list-peers
Node          Address                              State       Voter
----          -------                              -----       -----
my-vault-0    my-vault-0.my-vault-internal:8201    leader      true
my-vault-1    my-vault-1.my-vault-internal:8201    follower    true
my-vault-2    my-vault-2.my-vault-internal:8201    follower    true

自動削除も動作確認できました。
なおこの機能は、最小ノード数が3のようなので、1台に戻すときには使えない模様(3台未満の時点でHAではなくなるから関係ないけどね)

初期構築で自動unseal設定にする

前述の手順は非auto unsealのHashicorp Vaultをauto unsealにする-migrateを行うものでしたが、初期構築からauto unseal状態でデプロイももちろん可能です。

$ helm upgrade --install my-vault hashicorp/vault -n vault --create-namespace -f values.yaml
$ kubectl get pod -n vault -o wide 
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-0                                 0/1     Running   0          23s   10.42.4.28   9952de1bc166   <none>           <none>
my-vault-agent-injector-6b66d76649-7vq86   1/1     Running   0          23s   10.42.5.16   b513c8231aad   <none>           <none>

$ kubectl exec -n vault my-vault-0 -- vault status
Key                      Value
---                      -----
Recovery Seal Type       azurekeyvault
Initialized              false
Sealed                   true
Total Recovery Shares    0
:

初期デプロイ時は未初期化のため最初の初期化を行います。

$ kubectl exec -n vault my-vault-0 -- vault operator init
Recovery Key 1: ck667PevKK5uImgLyxWIuo5Gjnbtegi+m64koc5jC1GC
Recovery Key 2: Vz3NMGw7C2EiTMqcicF0oHbcTZrGXvvYBM3cT7ElITo6
Recovery Key 3: /HhvNK2UuQEAtYBSqbcbavEZ5b4SSB51gJA08jpPk6Vt
Recovery Key 4: GQz/MJfpysvc/cokvq1ynMFV65nId6JS+JD84DGnExE8
Recovery Key 5: yDilNc0RQpEa1B+Fyle35fMQvkJ4s1GTmKaAmlBwXRHP

Initial Root Token: hvs.a1CBYXlcb0t7nq5eMGZdLxqC

Success! Vault is initialized

Recovery key initialized with 5 key shares and a key threshold of 3. Please
securely distribute the key shares printed above.

すると、この時点でunsealとなります。

$ kubectl get pod -n vault -o wide
NAME                                       READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
my-vault-agent-injector-6b66d76649-7vq86   1/1     Running   0          97s   10.42.5.16   b513c8231aad   <none>           <none>
my-vault-0                                 1/1     Running   0          97s   10.42.4.28   9952de1bc166   <none>           <none>

まとめ

以上、Hashicorp VaultとAzure Key Vaultを使った自動unsealや自動joinについて動作確認してみました。
auto unsealはクラウドサービスと連携、クラスターへのauto joinはretry_join設定、クラスターかrのauto removeはautopilot機能で実現できることを確認しました。

最後に繰り返しになりますが、本文中はunseal keyやroot tokenを全部書き出してますが、これは記事用のためで、実際は機密情報なので厳重に管理してください。

参考

[Kubernetes] K3sのバージョン指定インストール

K3sをインストールする際は、特に指定がない場合は「最新安定板」がインストールされますが、これはKubernetesの最新版の1個前だったりします。
このエントリ執筆時点(2024.02.08)のKubernetesのバージョンは1.29.1が最も新しいですが、K3sのインストールでは同じタイミングでリリースされた1.28系の最新版の1.28.6がインストールされます。

[zaki@fedora-dev3 ~]$ curl -sfL https://get.k3s.io | sh -
[INFO]  Finding release for channel stable
[INFO]  Using v1.28.6+k3s2 as release
[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.28.6+k3s2/sha256sum-amd64.txt
[INFO]  Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.28.6+k3s2/k3s
[INFO]  Verifying binary download

...

[zaki@fedora-dev3 ~]$ sudo kubectl get node
NAME          STATUS   ROLES                  AGE   VERSION
fedora-dev3   Ready    control-plane,master   26s   v1.28.6+k3s2

ここでは最新の1.29.1をインストールしたいとか、例えば運用で使っているバージョンと同じ構成を新規に作りたい場合などにバージョン指定でインストールしたい場合ついて簡単にまとめました。
バージョンの指定は環境変数で指定します。

docs.k3s.io

方法1:チャンネル指定

最新を使いたい場合はこれが楽。
環境変数 INSTALL_K3S_CHANNEL にはstable / latest / testingが指定可能です。
未指定の場合はデフォルトのstableになり最新安定板になります。
最新版(現時点での1.29.1)を使いたい場合はlatestを指定してインストールします。

[zaki@fedora-dev3 ~]$ curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=latest sh -
[INFO]  Finding release for channel latest
[INFO]  Using v1.29.1+k3s2 as release
[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.29.1+k3s2/sha256sum-amd64.txt
[INFO]  Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.29.1+k3s2/k3s
[INFO]  Verifying binary download

...

[zaki@fedora-dev3 ~]$ sudo kubectl get node
NAME          STATUS   ROLES                  AGE   VERSION
fedora-dev3   Ready    control-plane,master   37s   v1.29.1+k3s2

どのバージョンがインストールされるかはチャンネル一覧を見るのが早いかも。

方法2:バージョン指定

バージョンをピンポイントで指定する場合は環境変数 INSTALL_K3S_VERSION を使います。
例えばちょっと前のバージョンのv1.26.9を使いたい場合。(2023年9月リリース)

[zaki@fedora-dev3 ~]$ curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.26.9+k3s1  sh -
[INFO]  Using v1.26.9+k3s1 as release
[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.26.9+k3s1/sha256sum-amd64.txt
[INFO]  Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.26.9+k3s1/k3s
[INFO]  Verifying binary download

...

[zaki@fedora-dev3 ~]$ sudo kubectl get node
NAME          STATUS   ROLES                  AGE   VERSION
fedora-dev3   Ready    control-plane,master   15s   v1.26.9+k3s1

入れたいK3sのバージョンが分かってる場合にそれを指定するのに使用する環境変数ですが、KubernetesバージョンだけわかっててK3sのバージョン(末尾が+k3s1)が不明な場合は、GitHubのTags一覧から探せばよさそう。(ほかの探し方はわからなかった)

github.com

Proxmox VEのVMのIPアドレスをサマリーに表示する

特にESXiから移行したVMなんかで表示されない「サマリー画面」のIPsですが、次の2点を対応すればVMIPアドレスが表示されます。ほぼ小ネタ。

1. VMオプションの「QEMU Guest Agent」の有効化

[オプション]->[QEMU Guest Agent]の値がデフォルトは無効になってますが、これを有効(「QEMU Guest Agentを使用」にチェック)にします。
オプションがいろいろありますが、特に変更せずにデフォルトの状態でOKです。

2. VMのOSにqemu-guest-agentパッケージをインストール

qemu-guest-agentパッケージをインストールします。
RHEL系(dnf)もDebian系(apt)もパッケージ名は同じ。
インストールしたらサービスを有効にします。

systemctl start qemu-guest-agent

これでIPアドレスが表示されるようになります。


Windowsはわからん。ごめん。