zaki work log

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

[Ansible Builder / AWS] CodeCommit + CodeBuild 環境でEEの自動ビルドとECRへpush (起動は手動)

プライベートではGitHub Actions使ったEEの自動ビルドを1年前に試したけど、業務だとCodeCommit環境を使ってたりするので、せっかくなのでAWSのサービスを使ったビルド環境を試してみた。

初めに軽く検索した感じだと、CodeBuildではコンテナで処理がされるという情報を見かけたので、コンテナビルドはどうするのかと思ったけど、普通にCodeBuild内でコンテナビルドも動くようだ。

やりたいことは以下。

  • EEのソースはCodeCommitのものを使用
  • Ansible Builderをインストール
  • Red Hat RegistryからEEとRunnerのベースイメージをpull
  • イメージビルド
  • ビルドが完了したらECRへpush

これをCodeBuildを使って処理する。

CodeCommit

ポイントはビルドの定義を行うbuildspec.ymlファイル。
ほかは基本的には特記事項なし。

Buildspec

buildspec.ymlファイルを作成。
中身はこんな感じ。 この中身に従ってCodeBuildが動作する。 GitHub Actionsにおける.github/workflows/main.ymlみたいな位置づけのファイル。

rh_account,rh_username,rh_passwordについては、あとでSecret Managerで使うキー文字列。

version: 0.2

env:
  secrets-manager:
    USERNAME: rh_account:rh_username
    PASSWORD: rh_account:rh_password

phases:
  pre_build:
    commands:
    - pip install ansible-builder
    - docker login registry.redhat.io -u ${USERNAME} -p ${PASSWORD}
    - aws ecr get-login-password --region ap-northeast-1 | docker login ********.***.ecr.ap-northeast-1.amazonaws.com --username AWS --password-stdin

  build:
    commands:
    - echo "build image"
    - ansible-builder build -t ********.***.ecr.ap-northeast-1.amazonaws.com/sample-repository:develop

  post_build:
    commands:
    - echo "push image to ecr"
    - docker push ********.***.ecr.ap-northeast-1.amazonaws.com/sample-repository:develop

execution-environment.yml

お題はterraformを実行できるEEを作る。 今のところ必要がないので定義ファイルはver1のまま。

version: 1
build_arg_defaults:
  EE_BASE_IMAGE: registry.redhat.io/ansible-automation-platform/ee-minimal-rhel8:2.16.5-1
  EE_BUILDER_IMAGE: registry.redhat.io/ansible-automation-platform/ansible-builder-rhel8:1.2.0-75

ansible_config: 'ansible.cfg'

dependencies:
  galaxy: requirements.yml
  python: requirements.txt
  system: bindep.txt

additional_build_steps:
  prepend:
    - RUN microdnf install -y yum-utils
    - RUN yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
    - RUN dnf -y install terraform

requirements.yml

---
collections:
- community.general

bindep.txt

git

CodeBuild

プロジェクトを作成

プロジェクト名に任意の名前を指定。
ソースプロバイダに「AWS CodeCommit」を指定、リポジトリとブランチを入力する。
(ちなみにGitHubなど外部のリポジトリも選択可能)

環境はデフォルトでOK (というかデフォルト以外の選択を試していない)
「追加設定」をクリックし、必要に応じてパラメタとして使用する環境変数を設定する。
今回は、registry.redhat.ioにログインするための認証情報を外部からセットするため、環境変数でなくSecret Managerを使用する予定。これも環境変数のところで「タイプ」から「Secret Manager」を選択することで使用できる。
Secret Manager側が未設定なので、とりあえず今は何も設定せずに進める。(あとから編集可能)
ほかにはビルドするマシンサイズ(CPU/メモリ)を選択することもできる。デフォルトはRAM3GB/2vCPUs

Buildspecは「buildspecファイルを使用する」を選択。

他はデフォルトで「ビルドプロジェクトを作成する」を押下。

作成完了したら、「サービスロール」の文字列を確認しておく。
(次のSecret Managerからのアクセス許可で使用する)

Secret Managerの作成とアクセス権の設定

registry.redhat.ioにログインするための認証情報を作成する。

「新しいシークレットを保存する」から、「その他のシークレットのタイプ」を選択。
key/value形式のデータを登録できるので、ユーザー名とパスワードそれぞれキーと値を入力。

「暗号化キー」はデフォルトのまま「次」押下。

シークレットの設定で名称を入力し、「リソースのアクセス許可」で、CodeBuildからのアクセスを許可する編集を行う。
「許可を編集」を押下し、"Statement"へ以下を入力する。

{
    "Version":"2012-10-17",
    "Statement": [ {
        "Effect" : "Allow",
        "Principal" : {
            "AWS" : "CodeBuildのサービスロールのARN"
        },
        "Action" : "secretsmanager:GetSecretValue",
        "Resource" : "*"
    } ]
}

「次」で進んだ先はデフォルトでOKなので、そのまま「保存」でSecretを作成する。

※ ちなみに環境変数だと機密情報が「見えてしまう」のでSecret Managerを使おう、という文脈のはずだけど、他のIAMユーザーでもAWSのマネジメントコンソールからSecret Managerに保存している値はどうも参照できるっぽいので、個人に割り当てられてるパスワード情報とかは…どうすればいいんだ?

CodeBuildでSecret Manager値参照の設定

プロジェクトの編集の、環境の追加設定以下にある環境変数パートで、以下入力

名前 タイプ
RH_ACCOUNT (任意) シークレットの名前 (上の例だとrh_account) Secret Manager

これを入力して「プロジェクトを更新する」押下で保存。

CodeBuildからECRのアクセス権の設定

ビルドしたイメージをpushするために、CodeBuildの処理内からECRへのアクセス権を設定する。
具体的にはaws ecr get-login-passwordを使えるようにする設定。 (無いとno identity-based policy allows the ecr:GetAuthorizationToken actionエラー)

まず作成したCodeBuildのプロジェクトのサービスロール(リンクになってる)をクリックし、サービスロールのページへ移動する。

サービスロールの設定では「許可ポリシー」が(デフォルトのポリシーと、↑でセットしたSecret Managerの)2つセットされてるはずで、ここでさらに「許可を追加」->「ポリシーをアタッチ」押下。

検索欄にcontainerregistryを入力して出てくるAmazonEC2ContainerRegistryPowerUserをチェックして「許可を追加」する。

これで準備OK

ビルド

CodeBuildのプロジェクトに戻り、「ビルドを開始」を押下。

問題がなければansible-builderのインストール、各コンテナレジストリへのログイン、ビルドが行われ、最後にイメージをECRへpushするところまで処理される。

ECRでもpushされたイメージを確認できる。

まとめ

現状まだ処理の起動はボタンぽちが必要だけど、それまでAnsible Builderを用意して、コードをGitから持ってきて、ビルドのコマンドラインを入力・tagを設定してpushという手順だったのが、ボタン押下1アクションで全部終わるのはだいぶラクチン。しかもローカルPCでやるより断然速い。(スペックの問題)

Gitのpushやtagの作成に反応させるにはCodeBuildの設定にはなさそうだったので、おそらくCodePipelineを使うと思う。これもそのうちやってみる。

ちなみに一番ハマったのはSecret Managerからの値の取得だった。キーの関係がよくわからず試行錯誤。。


ところで1年前に書いたGitHub ActionsのEEイメージビルド、これも4月16日だったんだねぇ(16日中の公開は間に合わなかったけど笑)

zaki-hmkc.hatenablog.com

参考

docs.aws.amazon.com

docs.aws.amazon.com

docs.aws.amazon.com

dev.classmethod.jp

qiita.com

[Proxmox VE] Cloud-Initのvendorデータを使ってサブスクリプション済みRHELのVMをサクッと作成する

Proxmox VEのWeb UIから設定できるCloud-Init項目は以下の通りで、最低限必要な設定はできるけど、追加パッケージの指定とかがデフォルトでは現状できない。

  • ユーザ
  • パスワード
  • DNSドメイン
  • DNSサーバ
  • SSH公開鍵
  • プロビジョニング時のパッケージアップグレード(apt upgradeやdnf update)の実行有無
  • IPアドレス設定 (固定 / DHCP)

ただしCloud-Initの機能そのものにはもっと様々な初期設定を指定でき、例えばRHELのサブスクリプションはrh_subscriptionで設定可能。

cloudinit.readthedocs.io

今回はこれをProxmox VEから処理するための設定を使って、Proxmox VEでサブスクリプション済みのRHELVMをテンプレートとCloud-Initを使ってプロビジョニングする仕組みを確認。

pve.proxmox.com

RHELクラウドイメージ

カスタマポータルのDownloadから、Red Hat Enterprise Linuxへ進む。
リストから普通のイメージでなく、「Virtualization Images」の「Red Hat Enterprise Linux . KVM Guest Image」をダウンロード。

今回は諸事情により8.9を使用。

カスタムCloud-Initの設定

テンプレート作成部分は先日書いた以下のエントリ参照。

zaki-hmkc.hatenablog.com

テンプレートの作成

テンプレート9010の「rhel8.9-template」を作成。

qm create 9010 --name rhel8.9-template --memory 2048 --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-pci
qm set 9010 --scsi0 S870QVO2TB:0,import-from=/mnt/pve/pecorino-dev/template/iso/rhel-8.9-x86_64-kvm.qcow2
qm set 9010 --ide2 S870QVO2TB:cloudinit
qm set 9010 --boot order=scsi0
qm set 9010 --serial0 socket --vga serial0
qm template 9010

ここでUIで可能なCloud-Initの設定もセットする。

カスタム設定配置用snippetsの設定

カスタム設定ファイルを置くために、ストレージの設定でスニペットを有効にする。今回はNFSのストレージに設定。
(このストレージはテンプレートのストレージと異なっていても問題ない)

有効にするとディレクトリが作成される。

root@pve:~# ls -al /mnt/pve/pecorino-dev/snippets/
total 8
drwxr-xr-x  2 root root 4096 Apr 14 14:27 .
drwxrwxrwx 13 root root 4096 Apr 14 14:27 ..

vendor設定ファイルの作成

認証情報がplaintextのままなのはちょっと気になるところだけど個人環境なのでとりあえず。
サブスクリプション割り当てのためのrh_subscriptionと、追加パッケージのpackagespodmanを初期状態でインストールされるようにしてみる。

root@pve:~# ls -l /mnt/pve/pecorino-dev/snippets/
total 4
-rw-r--r-- 1 root root 99 Apr 14 17:10 rhel_subscription_vendor_template.yaml
root@pve:~# cat /mnt/pve/pecorino-dev/snippets/rhel_subscription_vendor_template.yaml 
#cloud-config
rh_subscription:
  username: ユーザー名を記載
  password: パスワードを記載
  auto-attach: True

packages:
- podman

vendorデータのセット

作成したvendorデータ定義が書かれたYAMLファイルを、テンプレートのCloud-Initへセットする。
ファイルシステム上のパスでなく、ストレージ名とその中でのパスを指定する。

root@pve:~# qm set 9010 --cicustom "vendor=pecorino-dev:snippets/rhel_subscription_vendor_template.yaml"
update VM 9010: -cicustom vendor=pecorino-dev:snippets/rhel_subscription_vendor_template.yaml

これですべての準備が完了。

※ ただし、Web UIのCloud-Initの画面では、vendor設定に関して現バージョンでは確認はできない。

VMのクローン

とりあえずUIのクローンでVM IDとホスト名を入力してVMを作成し、電源投入

この通りSubscription Managementのログが表示され、(Cloud-Initで更新を有効にしてるので)パッケージアップデートも動作しはじめる。
Red Hatのカスタマポータルでも、システムが登録されていることを確認できる。

パッケージアップデートを有効にしていると処理時間の関係で時間は多少かかるが、podmanも自動的にインストールされる。

[zaki@rhel8-dev ~]$ podman --version
podman version 4.6.1

環境

  • PVE 8.1.10
  • RHEL 8.9 (Virtualization Images)

Proxmox VEのCloud-Initは、vendorデータのカスタム設定でWeb UIから設定・確認できない項目も初期設定として使えることが確認できた。
ちなみにuserもファイルから設定できるが、これをやるとWebのUIから設定ができなくなる(qm set <vmid> --cicustomでセットした内容で上書きされる)ので、画面から設定できない項目は基本的にvendorデータを使用するのが良さそう。

参考URL

ainoniwa.net

ainoniwa.net

この2つの記事は大変参考になりました。
コマンドはCustom Cloud-Init COnfigurationに記載がある。が、確かにわかりづらい…

[AWS / Terraform] EC2作成時のユーザーデータ内で動的に割り当てられるIPアドレスやパブリックDNSの参照とIMDSv2の設定

TerraformでEC2プロビジョニング時に、ユーザーデータ内でパブリックDNS名を参照しようとしたらすんなり実装できなかったのでメモ。

AWSでEC2をプロビジョニングする際にホスト上の初期処理を投入したい場合は、実行したいコマンドをシェルスクリプトとして記述できるユーザーデータが便利。 ただし、Terraformなどから利用する場合にテンプレートを使ってパラメタも指定できてさらに便利に使えるけど、EC2をデプロイした結果決まる値を渡すことはできない。たとえばIPアドレスとか。

TerraformのNG実装例

よくある(?)例として、EC2上にデプロイするアプリケーションに自信のホスト名やアドレスを設定したいケースで、テンプレートにpublic_dnsを指定してもエラーになる。

resource "aws_instance" "server" {
  ami                         = var.server_ami_id
  instance_type               = var.server_instance_type
  subnet_id                   = aws_subnet.subnet.id
  associate_public_ip_address = true

[...]

  user_data = templatefile("${path.module}/user_data.sh", {
    public_dns = aws_instance.server.public_dns
  })

上記の定義だと以下のように自身を参照できずにエラーになる。

╷
│ Error: Self-referential block
│ 
│   on online/main.tf line 115, in resource "aws_instance" "server":
│  115:     public_dns = aws_instance.server.public_dns
│ 
│ Configuration for aws_instance.server may not refer to itself.
╵

メタデータの参照

検索すると「ネットワークインタフェースを先に単体で作成・アドレスを決定し、EC2を作成してアタッチ」という方法もあったけど、インフラ構成が変わるのが個人的には微妙だったので、「ユーザーデータ内でメタデータを返すAPIを使用する」で対処。

docs.aws.amazon.com

[ec2-user@ip-10-1-1-227 ~]$ curl http://169.254.169.254/latest/meta-data/
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hibernation/
hostname
identity-credentials/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
public-keys/
reservation-id
security-groups
services/
system

このように、様々な情報を参照できるエンドポイントが提供されている。

DNS名であればpublic-hostnameIPアドレス(GIP)ならpublic-ipv4という具合。

[ec2-user@ip-10-1-1-227 ~]$ curl http://169.254.169.254/latest/meta-data/public-hostname
ec2-**-**-**-**.ap-northeast-1.compute.amazonaws.com

プライベートIPアドレスも取れる。

[ec2-user@ip-10-1-1-227 ~]$ ip a s eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
    link/ether 06:c9:30:bd:85:55 brd ff:ff:ff:ff:ff:ff
    inet 10.1.1.227/24 brd 10.1.1.255 scope global dynamic noprefixroute eth0
       valid_lft 1931sec preferred_lft 1931sec
    inet6 fe80::4c9:30ff:febd:8555/64 scope link 
       valid_lft forever preferred_lft forever
[ec2-user@ip-10-1-1-227 ~]$ curl http://169.254.169.254/latest/meta-data/local-ipv4
10.1.1.227

このAPIを使えば、テンプレートの引数も使わず、例えばIPアドレスをセットしたい箇所で $(curl http://169.254.169.254/latest/meta-data/public-ipv4) と記述すれば、EC2プロビジョニング時にはEC2のグローバルIPに変換される、という寸法。


と、ここまで書いてシェアしたところ、Xで「IMDSはv2を使うように」とコメントをいただいたので追加調査。 (認証不要のv1の方で動いたのでこれでいいやと思ってブログにしたのが本音w)

コメントありがとうございます!

IMDSv2

上記はIMDS(Instance Metadata Service)はv1の例で、現在はv2が推奨。

docs.aws.amazon.com

IMDSv2を使うにはまず認証トークンを取得する必要がある。

TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 600")

このトークンをヘッダに指定して実行すればOK

[ec2-user@ip-10-1-1-229 ~]$ curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-ipv4
52.*.*.*

ここでトークンの期限を600秒に指定しているので、時間が過ぎれば使用不可になる。

[ec2-user@ip-10-1-1-229 ~]$ curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-ipv4
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
  <title>401 - Unauthorized</title>
 </head>
 <body>
  <h1>401 - Unauthorized</h1>
 </body>
</html>

IMDSv2を必須にするにはEC2の設定で変更でき、v2を必須にするとトークン不要のv1のアクセスが使用不能になる。

[ec2-user@ip-10-1-1-229 ~]$ curl http://169.254.169.254/latest/meta-data/public-ipv4
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 <head>
  <title>401 - Unauthorized</title>
 </head>
 <body>
  <h1>401 - Unauthorized</h1>
 </body>
</html>

IMDSv2必須設定

設定変更はUIであれば以下の通り。

Terraformの場合はmetadata_optionsで指定可能。

resource "aws_instance" "server" {

  [...]

  metadata_options {
    http_tokens = "required"
  }

まぁ商用だったらEC2デプロイ時に決定するIPアドレスDNS名を使うことはなくて別途レコード作成した自前のDNS名を使うんだろうけど、使い捨ての検証環境とかならこんな感じで十分かな。

discuss.hashicorp.com

[Proxmox VE] テンプレートとクローンとCloud-Initを使ってVMをサクサクシュッと生やす (Fedora / Ubuntu)

Proxmox VEには、VMのテンプレート化とクローン機能があり、さらにCloud-Initを使って初期設定込みで簡単にVMをプロビジョニングできる機能がある。
もうすぐFedora40やUbuntu 24.04がリリースされそうなので、その時にサラサラとテンプレート作成できるように、この機能を使う準備についておさらい。
といっても、ドキュメントの通り順番にコマンド実行していけば作成できる。

pve.proxmox.com

ちなみにここではUbuntuの情報はあふれてるので、ここではFedora 40 betaでお試し。 (Ubuntuの場合も参考程度に記載してる)

テンプレートの作成

まずはCloudイメージを使ったVMを作り、それをテンプレートに変換、という手順。
UIが用意されてるか微妙なので、ドキュメント通りCLIで実行する。

ベースになるCloudイメージの入手

Proxmox VEのテンプレート・クローン・Cloud-Init機能を使うためのベースイメージはクラウドイメージを使用する。
Fedoraはここから入手。

fedoraproject.org

ここからbeta版のFedora Cloud 40 QEMU (qcow2形式)をダウンロードする。(betaの場合は「Show Beta downloads」のボタンで切り替える)

curl -LO https://download.fedoraproject.org/pub/fedora/linux/releases/test/40_Beta/Cloud/x86_64/images/Fedora-Cloud-Base-Generic.x86_64-40-1.10.qcow2

ちなみにUbuntuクラウドイメージはこちらから。

cloud-images.ubuntu.com

Ubuntu 22.04 LTS (Jammy)であれば以下。

curl -LO https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img

試してないけど 24.04 LTSだとこれかな?今はdaily buildっぽいけど。

curl -LO https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img

これをローカルやNFSストレージなど、PVEで使える場所に配置。

テンプレート元にするVM作成

普通のVMを作るときはIDはだいたい3桁にしてるかもだけど、ドキュメントのサンプルは9000と4桁になってて、実際桁数が異なっているとわかりやすいので、それに倣って4桁IDのテンプレート作成。
手元の環境ではすでにいくつか作成済みなので、ここでは9002を使用。

qm create 9002 --name fedora40beta-template --memory 2048 --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-pci

これで仮想マシン一覧のところに、ID:9002のVMが作成される。

イメージのセット

ダウンロードしたクラウドイメージをローカルストレージ(以下はlocal-lvm)のVMとしてインポート。
import-fromにPVE上で参照できるイメージのパスを指定。

qm set 9002 --scsi0 local-lvm:0,import-from=/mnt/pve/pecorino-dev/template/iso/Fedora-Cloud-Base-Generic.x86_64-40-1.10.qcow2

実行例

root@pve:~# qm set 9002 --scsi0 local-lvm:0,import-from=/mnt/pve/pecorino-dev/template/iso/Fedora-Cloud-Base-Generic.x86_64-40-1.10.qcow2
update VM 9002: -scsi0 local-lvm:0,import-from=/mnt/pve/pecorino-dev/template/iso/Fedora-Cloud-Base-Generic.x86_64-40-1.10.qcow2
  Logical volume "vm-9002-disk-0" created.
transferred 0.0 B of 5.0 GiB (0.00%)
transferred 51.7 MiB of 5.0 GiB (1.01%)
transferred 102.9 MiB of 5.0 GiB (2.01%)
transferred 154.6 MiB of 5.0 GiB (3.02%)
:
:
transferred 5.0 GiB of 5.0 GiB (99.85%)
transferred 5.0 GiB of 5.0 GiB (100.00%)
transferred 5.0 GiB of 5.0 GiB (100.00%)
scsi0: successfully created disk 'local-lvm:vm-9002-disk-0,size=5G'
root@pve:~#

これでVMにディスクがセットされる。

Cloud-Init CD-ROMドライブの追加

Cloud-Initの情報をVM起動時にセットするためのCD-ROMドライブを作成する。

qm set 9002 --ide2 local-lvm:cloudinit

実行例

root@pve:~# qm set 9002 --ide2 local-lvm:cloudinit
update VM 9002: -ide2 local-lvm:cloudinit
  Logical volume "vm-9002-cloudinit" created.
ide2: successfully created disk 'local-lvm:vm-9002-cloudinit,media=cdrom'
generating cloud-init ISO
root@pve:~# 

これで「CloudInitデバイス」が追加される。

さらに、追加したイメージ(ディスク)からブートするように、ブート順を変更。

qm set 9002 --boot order=scsi0

実行例

root@pve:~# qm set 9002 --boot order=scsi0
update VM 9002: -boot order=scsi0
root@pve:~# 

シリアルコンソールの設定を追加。

qm set 9002 --serial0 socket --vga serial0

実行例

root@pve:~# qm set 9002 --serial0 socket --vga serial0
update VM 9002: -serial0 socket -vga serial0
root@pve:~# 

テンプレートへの変換

最後にテンプレートへ変換。

qm template 9002

実行例

root@pve:~# qm template 9002
  Renamed "vm-9002-disk-0" to "base-9002-disk-0" in volume group "pve"
  Logical volume pve/base-9002-disk-0 changed.
  WARNING: Combining activation change with other commands is not advised.
root@pve:~# 

warning出てるけどいつも出るのでたぶん無視していい。(本当?) 変換が完了すると、「仮想マシン一覧」のアイコンが変わりテンプレートになる。

これでいつでもクローン機能を使ってVMをポコポコ生やす準備が「ほぼ」完了。

テンプレートの設定

ベースになるイメージの設定によるけど、CPUやメモリ、ストレージなどがデフォルト値のままで、特にストレージは全然足りない設定になってることが多いので、自分が使いたい初期値に更新する。

ここからはWeb UIで実施

VM設定

VMの設定は基本的にUIでVMを新規作成するときに指定するものと同じで、主に以下の項目。「ハードウェア」から更新する。
(ディスクは「ディスクの動作」->「リサイズ」で増分を指定。減らすのは少なくともUIは無理そう?)

  • メモリ
  • プロセッサ(cpu数/種別)
  • ディスクサイズ

Fedora 40 Betaだと、クラウドイメージからVMを作るとメモリが2GB、CPUが1socket/1core、ストレージが5GBになってるので、欲しいスペックの値に更新する。
この値は「クローン」をした時の初期値になるため、「VMをサクッと作ったときに設定されていたい値」をセットする。
クローン後にも設定できるので、「今回はパワーあるVMが欲しい」みたいな個別設定は、クローンのあとに設定すればよい。

特にRHEL系v9のCPU種別で「x86-64-v2-AES」でないとエラーになるパターンがあるので、CPU種別は設定しておくのと、デフォルトのストレージサイズは結構少ないので、20GBとか検証その他で困らないサイズを指定しておくのが良い。
(イメージの種類によってはデフォルト値のままだと、ブート中にディスクフルになったりするサイズだったりする笑)

他には「オプション」の「QEMU Guest Agent」とかの有効化ね。

Cloud-Init設定

今回のキモがここで、VMをプロビジョニングした時点でユーザーアカウントやネットワーク設定など簡単なOS設定が完了した状態にする、パブリッククラウドのコンピューティングリソースでおなじみの機能を設定。
Proxmox VEのWeb UIでは以下の項目を設定できる。

  • ユーザ
  • パスワード
  • DNSドメイン
  • DNSサーバ
  • SSH公開鍵
  • プロビジョニング時のパッケージアップグレード(apt upgradeやdnf update)の実行有無
  • IPアドレス設定 (固定 / DHCP)

アカウント関連は自分だけが使う環境であればテンプレートの時点でセットしておくと便利。
IPアドレスDHCPでなければVMごとに変わってくるはずなので、テンプレートの時点では空欄にしておいて、クローン後に設定することが多いかも。
この辺りは使い方次第。

ここまで設定できればテンプレートの完成。 あとは収穫。

VMのクローンと起動

仮想マシン一覧」から右クリックメニューの「クローン」で、VMIDとマシン名を入力すればVMがプロビジョニングされる。
(実は「モード」がよくわかってない。完全クローンだと結構時間がかかる)

このVMは前述テンプレートの設定で入力したCPUやメモリ・ストレージサイズ、Cloud-Initの内容がそっくりコピーされているので、テンプレートの内容のままで良ければそのまま開始すればOK
ネットワーク設定などVM毎の設定が必要であれば、クローン直後(初回起動前)に設定する。
(Cloud-Initは初回起動時のみに処理されるので)

今回はネットワーク設定を更新して起動。

コンソールにアクセスすれば、起動は割とすぐ完了してログインプロンプトが表示されるので、Cloud-Initで設定したユーザー名とパスワードでログインができる。
ただ、Cloud-Init自体はログインプロンプト表示後も非同期で処理されており、特にパッケージアップデートを有効にしていると、コンソールログイン後も並行して処理される。

SSHサーバーも早い段階で起動しているので、リモートからSSHアクセスが(公開鍵を設定していればその鍵を使った公開鍵認証で)可能。

環境

PVE 8.1.10 無償ライセンス版


というわけでProxmox VEのテンプレートとクローンとCloud-InitでVMを簡単にプロビジョニングできる機能についてのまとめ。
使える状態のVMを秒で作成できる手軽さは、コンテナ利用に近いものがあるスピード感で、OS設定のレイヤーから環境が欲しい場合はこの便利さはかなり感動もの。

ちなみに機能(感覚的にネットワークセキュリティ系)は結構少なかったりする。
iptablesやfirewalldは入ってないので、IaaSの同等機能を使う想定だったりするのかな?

[Kubernetes] DaemonSetで動いてるPodを停止(スケールを0に)したい

業務で元リソースはそのままでPodを一時的に停止していろいろ確認するなんやかやがあり、DeploymentやStatefulSetはkubectl scaleでレプリカ数を0にすれば良かったんだけど、DaemonSetってそういえばレプリカ0にできないというかそもそもレプリカの概念がなく、なんとかできそうにないかと思いつつ検索してみるとnodeSelectorを使ってデプロイできるノードを無くすという技がいくつかヒットしたので試してみた。

stackoverflow.com

まとめておくと以下でOK

# 停止
kubectl -n <namespace> patch daemonset <daemonset-resource> -p '{"spec": {"template": {"spec": {"nodeSelector": {"non-existing": "true"}}}}}'

# 起動
kubectl -n <namespace> patch daemonset <daemonset-resource> --type json -p='[{"op": "remove", "path": "/spec/template/spec/nodeSelector/non-existing"}]'

サンプルDaemonSet

GitHubに置いてあるDaemonSet版webサーバーマニフェストを使用。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    app: http-daemon
  name: http-daemon
spec:
  selector:
    matchLabels:
      app: http-daemon
  template:
    metadata:
      labels:
        app: http-daemon
    spec:
      containers:
      - image: httpd
        name: httpd
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: http-daemon
  name: http-daemon
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
    name: http
  selector:
    app: http-daemon
  type: ClusterIP
$ kubectl apply -f https://raw.githubusercontent.com/zaki-lknr/k8s-samples/master/sample-web/httpd-daemonset/sample-http.yaml
daemonset.apps/http-daemon created
service/http-daemon created

zaki@cloud-dev2:~$ kubectl get pod -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP          NODE           NOMINATED NODE   READINESS GATES
http-daemon-g6glw   1/1     Running   0          23s   10.42.0.6   730dd035b3ef   <none>           <none>
http-daemon-rxxbk   1/1     Running   0          23s   10.42.1.6   e35370123528   <none>           <none>

このDaemonSetリソースに対してレプリカ数を操作しようとしてもエラーになる。

zaki@cloud-dev2:~$ kubectl scale ds http-daemon --replicas=0
Error from server (NotFound): the server could not find the requested resource

ノードセレクタを設定

kubectl patchでノードセレクタを追加

zaki@cloud-dev2:~$ kubectl -n default patch daemonset http-daemon -p '{"spec": {"template": {"spec": {"nodeSelector": {"non-existing": "true"}}}}}'
daemonset.apps/http-daemon patched

するとPodが停止する。

zaki@cloud-dev2:~$ kubectl get pod -o wide
NAME                READY   STATUS        RESTARTS   AGE     IP       NODE           NOMINATED NODE   READINESS GATES
http-daemon-rxxbk   0/1     Terminating   0          2m18s   <none>   e35370123528   <none>           <none>
zaki@cloud-dev2:~$ kubectl get pod -o wide
No resources found in default namespace.

DaemonSetリソースはこの通り、ノードセレクタが設定され、DESIREDが0になる。

zaki@cloud-dev2:~$ kubectl get daemonset
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR       AGE
http-daemon   0         0         0       0            0           non-existing=true   4m5s

元に戻す

zaki@cloud-dev2:~$ kubectl -n default patch daemonset http-daemon --type json -p='[{"op": "remove", "path": "/spec/template/spec/nodeSelector/non-existing"}]'
daemonset.apps/http-daemon patched

ノードセレクタの設定を削除するとPodが再度デプロイされる。

zaki@cloud-dev2:~$ kubectl get daemonset
NAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
http-daemon   2         2         1       2            1           <none>          5m7s
zaki@cloud-dev2:~$ kubectl get pod -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP          NODE           NOMINATED NODE   READINESS GATES
http-daemon-j84kz   1/1     Running   0          7s    10.42.1.7   e35370123528   <none>           <none>
http-daemon-whp2q   1/1     Running   0          7s    10.42.0.7   730dd035b3ef   <none>           <none>

(余談) 停止中に対象ラベルをノードに設定すると…

もちろんラベルをセットしたノードではデプロイされる。

# podが停止している状態
zaki@cloud-dev2:~$ kubectl get pod 
No resources found in default namespace.

# ノードのラベル状態
zaki@cloud-dev2:~$ kubectl get nodes --show-labels 
NAME           STATUS   ROLES                  AGE   VERSION        LABELS
e35370123528   Ready    control-plane,master   11m   v1.29.1+k3s2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=k3s,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=e35370123528,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=true,node-role.kubernetes.io/master=true,node.kubernetes.io/instance-type=k3s
730dd035b3ef   Ready    <none>                 11m   v1.29.1+k3s2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=k3s,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=730dd035b3ef,kubernetes.io/os=linux,node.kubernetes.io/instance-type=k3s

# ラベル設定
zaki@cloud-dev2:~$ kubectl label node 730dd035b3ef non-existing=true
node/730dd035b3ef labeled
zaki@cloud-dev2:~$ kubectl get nodes --show-labels 
NAME           STATUS   ROLES                  AGE   VERSION        LABELS
e35370123528   Ready    control-plane,master   11m   v1.29.1+k3s2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=k3s,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=e35370123528,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=true,node-role.kubernetes.io/master=true,node.kubernetes.io/instance-type=k3s
730dd035b3ef   Ready    <none>                 11m   v1.29.1+k3s2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=k3s,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=730dd035b3ef,kubernetes.io/os=linux,node.kubernetes.io/instance-type=k3s,non-existing=true

# ラベルを設定したノードにはpodがデプロイされる
zaki@cloud-dev2:~$ kubectl get pod -o wide
NAME                READY   STATUS              RESTARTS   AGE   IP       NODE           NOMINATED NODE   READINESS GATES
http-daemon-jwg8d   0/1     ContainerCreating   0          9s    <none>   730dd035b3ef   <none>           <none>
zaki@cloud-dev2:~$ kubectl get pod -o wide
NAME                READY   STATUS    RESTARTS   AGE   IP          NODE           NOMINATED NODE   READINESS GATES
http-daemon-jwg8d   1/1     Running   0          29s   10.42.0.8   730dd035b3ef   <none>           <none>

環境

K3s v1.29(コンテナ版)で確認

zaki@cloud-dev2:~$ kubectl get node
NAME           STATUS   ROLES                  AGE    VERSION
730dd035b3ef   Ready    <none>                 115s   v1.29.1+k3s2
e35370123528   Ready    control-plane,master   2m6s   v1.29.1+k3s2

nodeSelectorを使ってPodをデプロイするノードを制御、みたいなまとめを書いてたような気がしてたけど気のせいだったか。。

(4/11追記) arm64版AWXをデプロイするだけなら自前のビルドは全く不要になってる件の補足とまとめ

2024.04.11追記

forum.ansible.com

AWX 24.2.0とAWX Operator 2.15.0がリリースされ、GitHub Container Registryにしかなかったarm64版awxイメージがquay.ioからもpullできるようになりました。

image repository tag
awx-operator quay.io/ansible/awx-operator 2.12.1以降
awx quay.io/ansible/awx 24.2.0以降
awx-ee quay.io/ansible/awx-ee 23.9.0以降

kustomizationとAWXリソースもかなりシンプルに記述できます。
というよりarm64版特有の記述がゼロになりました。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  # Find the latest tag here: https://github.com/ansible/awx-operator/releases
  - github.com/ansible/awx-operator/config/default?ref=2.15.0

# Set the image tags to match the git version from above
images:
  - name: quay.io/ansible/awx-operator
    newTag: 2.15.0

# Specify a custom namespace in which to install AWX
namespace: awx

AWX Operatorの2.15.0を使うと、awxとawx-eeはarm64版のある24.2.0がデフォルトで使用されます。

---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx-sample
  namespace: awx
spec:
  service_type: LoadBalancer
  loadbalancer_port: 8080

まとめ

必要なイメージは2024.03.21時点で以下のものがarm64対応してるのでこれ使えばおk

image repository tag デフォルト?
awx-operator quay.io/ansible/awx-operator:2.13.1 2.12.1以降 / latest /devel デフォルト
awx ghcr.io/ansible/awx devel 要指定
awx-ee quay.io/ansible/awx-ee 23.9.0 / 24.0.0 / latest デフォルト

本文

昨日書いたエントリで「awx-eeはarm版があるのでawxとawx-operatorだけビルドすればいい」とまとめてたけど、記事を見てくれた方からGitHub Container Registryに実は公式のarm64版イメージあるよと教えてもらって動作確認も手元でできたので、改めて2024年3月21日(JST)時点でのarm64版AWXの「デプロイ」の仕方についてまとめ。

kustomization

kustomization.yamlファイルはだいたい次のような感じ。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - github.com/ansible/awx-operator/config/default?ref=2.13.1
  #- github.com/ansible/awx-operator/config/default?ref=devel

 Set the image tags to match the git version from above
images:
- name: quay.io/ansible/awx-operator
  newTag: 2.13.1

namespace: awx

GitHubマニフェストは最新タグの2.13.1でも動くし、develでもOK
(他は未確認)

イメージについてはよくよく確認すると、Operatorも実はquayに上がってるイメージはバージョン2.12.1からarm64対応していた。
タグ一覧をみたときにタックスくんアイコンが並んでるものがマルチアーキテクチャ対応しているみたい。

なのでOperatorのバージョン(イメージタグ)は現在最新の2.13.1を指定していれば問題ないし、未指定の場合はlatestになるけどこれもarm64対応しているのでimages指定はまるっと無くても動く。

awx

AWXカスタムリソースについては、デフォルトではquayからawx本体をpullするけどこれはarm64対応していないので、ghcrからpullする指定が必須。
ただし2024.03.21時点ではghcrのイメージでもarm64対応しているのはdevelタグのイメージのみ。(latest/24.0.0のイメージはamd64のみ…そのうちマルチアーキテクチャ対応するかもだけど)

なのでこんな感じ。(service_type以下は環境にあわせる)

---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx-sample
  namespace: awx
spec:
  image: ghcr.io/ansible/awx
  image_version: devel
  service_type: LoadBalancer
  loadbalancer_port: 8080

これで無事にAWXをarm64アーキテクチャクラスタ上にデプロイでき、ジョブテンプレートも動作する。
めでたしめでたし🎉


AWXカスタムリソースでイメージの指定をしなかったら以下のようにエラーになる。

ubuntu@oci-l-a1-ubuntu:~/awx-ghcr$ kubectl get pod -n awx
NAME                                              READY   STATUS                  RESTARTS   AGE
awx-operator-controller-manager-8f8f4df5d-hpf4z   2/2     Running                 0          22m
awx-sample-postgres-15-0                          1/1     Running                 0          19m
awx-sample-task-cdd7d55d4-9t8hf                   0/4     Init:ImagePullBackOff   0          19m
awx-sample-web-56d647cc67-g8ztx                   1/3     ImagePullBackOff        0          19m

get eventするとこの通りプラットフォームアンマッチのエラーを確認できる。

19m         Warning   Failed                            pod/awx-sample-task-cdd7d55d4-9t8hf                          Failed to pull image "quay.io/ansible/awx:24.0.0": rpc error: code = NotFound desc = failed to pull and unpack image "quay.io/ansible/awx:24.0.0": no match for platform in manifest: not found

[追記あり] 2024年3月現在のAWXのarm64ビルドの状況 (AWX 24.0.0 / Operator 2.13.1)

2年前にKubernetesで動くAWXarmアーキテクチャのホストで動かそうとした際には、armプラットフォーム用のコンテナイメージが何一つ用意されてなく全部自前でビルドしてた。が、2024年現在、状況が結構変わってかなり簡単になってたので、今時点でどうすればarmプラットフォームにイメージビルドおよびAWXをデプロイできるかをまとめた。

2年前(しかも同じ3月20日だ笑)の状況は以下。

zaki-hmkc.hatenablog.com

現状ではawx-eeイメージのarmプラットフォーム版が公開されているため、awxawx-operatorの2つのイメージをビルドすればOK

※追記
ツイッター(現𝕏)でコメントを頂いて、awxawx-operatorも、QuayではなくGitHub Container Registryで公式arm64版イメージが公開されているとのこと。
実際に試すと動いた(awxはdevelタグ使用)ので、自前でビルドする必要は一切なくなったっぽい。ありがたい…

※※ さらにデプロイのみの件をまとめ

zaki-hmkc.hatenablog.com

以下はせっかくなのでawxとawx-operatorに関して、投稿時のものをとりあえずそのまま。。


そしてこの2つのイメージは依存関係の多かったawx-eeとは異なり、それぞれ単体でビルドすればよく、コマンドもmakeだけなので、かなり楽。

イメージビルド

awx-operator

github.com

2年前と変わらず、Makefileがあるのでこれを使ってビルドする。

git clone https://github.com/ansible/awx-operator.git -b 2.13.1
cd awx-operator

パラメタは基本的に不要で、以下はビルドするイメージの名前を設定してる(イメージのpush用)。

make docker-build IMG=zakihmkc/awx-operator:2.13.1

awx

github.com

以前ブログにまとめたときはMakefileはあるけど使い方が分からなかったということがあったので再検証。
結果としては、awx-kube-buildをターゲットにビルドすればOKだった。
ただし各パラメタはちゃんと設定してあげないとエラーになるので注意。

git clone https://github.com/ansible/awx.git -b 24.0.0
cd awx
make awx-kube-build VERSION=24.0.0 SETUPTOOLS_SCM_PRETEND_VERSION=24.0.0 DEV_DOCKER_TAG_BASE=zakihmkc COMPOSE_TAG=24.0.0

Makefileを見ればわかるけど、ビルドされたイメージは$(DEV_DOCKER_TAG_BASE)/awx:$(COMPOSE_TAG)という名前になる。この例だとzakihmkc/awx:24.0.0

AWXデプロイ

awx-eeはquayにあるものがそのまま使えるので、awx-operatorawxのみイメージ指定すればOK

AWXのデプロイ自体は以下も参照。

zaki-hmkc.hatenablog.com

現在のAWX Operator 2.13.1だと、PostgreSQLが15になって、初回起動時はmigrationのPodが起動するようになってる。

kustomization.yaml

awx-operatorのイメージを自前のイメージになるような変更を定義する。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  # Find the latest tag here: https://github.com/ansible/awx-operator/releases
  - github.com/ansible/awx-operator/config/default?ref=2.13.1

# Set the image tags to match the git version from above
images:
  - name: quay.io/ansible/awx-operator
    newTag: 2.13.1
    newName: zakihmkc/awx-operator

# Specify a custom namespace in which to install AWX
namespace: awx

awx.yaml

awxのイメージとバージョン(タグ)を指定する。

---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: arm-awx
  namespace: awx
spec:
  service_type: LoadBalancer
  loadbalancer_port: 8080
  image: zakihmkc/awx
  image_version: 24.0.0

アクセスすればこの通りポテト。

環境

ビルド環境

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=20.04
DISTRIB_CODENAME=focal
DISTRIB_DESCRIPTION="Ubuntu 20.04.6 LTS"

$ uname -m
aarch64

$ docker --version
Docker version 25.0.5, build 5dc9bcc

実行環境

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.4 LTS"

$ uname -m
aarch64

 k3s --version
k3s version v1.28.7+k3s1 (051b14b2)
go version go1.21.7

armプラットフォームでのビルドとデプロイ、これだけで良くなったのはだいぶ気軽に使えるようになって良いね。 Docker Hubにはイメージ置いてるので、上のマニフェストですぐ使えるようにしてある。


2年前のAWXのarm64ビルドの記事も3月20日でちょっとビビった(笑)