zaki work log

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

[Terraform] EC2プライベートIPアドレスの固定とパブリックIPの割り振り

AWSのEC2のプライベートIPは、使用するサブネットアドレスから自動で割り当てられるが、複数のEC2間で通信したい場合などであらかじめ固定したIPアドレスを使いたいという場合にTerraformでどう設定するかについて。

固定プライベートIPの設定

通常はaws_instanceリソースでパラメタ内にサブネットを指定してEC2を作るだけのところを、

  • aws_network_interfaceでアドレス指定のENIを作る
  • aws_instanceで↑のENIを使ってEC2を作る

という手順で構築すれば良い。
Terraformコードで実装すると以下で、10.1.3.1を固定で割り当てる場合(サブネットは10.1.3.0/24)。

private_ipsに指定しているアドレスはsample_subnetの範囲で使用可能なアドレス。

# IPアドレス固定化のためのENI作成
resource "aws_network_interface" "eni_fixed_ipaddr" {
  subnet_id         = aws_subnet.sample_subnet.id
  private_ips       = ["10.1.3.1"]
  security_groups   = [aws_security_group.sample_sg.id]
}

# network_interfaceで定義したENIを指定
resource "aws_instance" "sample_server" {
  ami               = var.server_ami_id
  instance_type     = var.server_instance_type
  key_name          = aws_key_pair.sample_ssh_keypair.id

  network_interface {
    device_index = 0
    network_interface_id = aws_network_interface.eni_fixed_ipaddr.id
  }
}

もともとaws_instanceのパラメタで指定していたサブネットとセキュリティグループ設定をENI側に移動する。

パブリックIP使用との併用

上記の定義でプライベートIPは固定できるが、同時にパブリックIPを割り当てるためのassociate_public_ip_addressを使うと、network_interfaceの設定と競合してエラーになる。

resource "aws_instance" "sample_server" {
  ami                         = var.server_ami_id
  instance_type               = var.server_instance_type
  key_name                    = aws_key_pair.sample_ssh_keypair.id
  associate_public_ip_address = true  # これがNG

  network_interface {
    device_index = 0
    network_interface_id = aws_network_interface.eni_fixed_ipaddr.id
  }
}

エラーは以下。

╷
│ Error: Conflicting configuration arguments
│ 
│   with module.dev.aws_instance.sample_server,
│   on dev/main.tf line 119, in resource "aws_instance" "sample_server":
│  119: resource "aws_instance" "sample_server" {
│ 
│ "network_interface": conflicts with associate_public_ip_address
╵

これを回避するには、EC2のパラメタではなく、使用するサブネットのパラメタで指定する。
aws_subnetのパラメタにmap_public_ip_on_launchがあり、これをtrueにすることでサブネット内のEC2は自動でパブリックIPが割り当てられる。

resource "aws_subnet" "sample_subnet" {
  vpc_id                  = aws_vpc.sample_vpc.id
  cidr_block              = "10.1.3.0/24"
  availability_zone       = data.aws_availability_zones.available.names[0]
  map_public_ip_on_launch = true
}

このサブネット使用したENIをEC2で使うように構成すれば「プライベートIPを固定しつつパブリックIPを使用」できるようになる。

環境

$ terraform --version
Terraform v1.9.1
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v5.43.0

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

[Ansible] community.general.slackモジュールでSlack通知を行うタスク

先日AAP/AWXの通知機能でSlack連携をまとめたので、ついでにPlaybookのタスクから任意のメッセージを通知する方法について。
使うモジュールは以下

docs.ansible.com

たとえばAAP/AWX構築Playbookで資材内にSlackのトークンを持っているのであれば、ついでに作業完了を通知するのに使う、とかに応用できる。

トークンの作成

以下の「投稿用bot作成」を参照

zaki-hmkc.hatenablog.com

Slack通知を行うタスクの作成

最小限の内容であれば以下の通り。
トークン、メッセージ、投稿先チャンネル名を指定する。

---
- hosts: localhost
  gather_facts: false

  tasks:
  - community.general.slack:
      token: "{{ oauth_token }}"
      msg: 届け!ときめき――
      channel: develop
      color: '#f0a6b8'

このタスクが実行されると、以下のようにSlackに通知される。

ボットのカスタマイズ

community.general.slackはメッセージ毎にアイコンやボット名のカスタムも可能。
ただし、タスク(モジュール)からアイコンや名前を指定するにはchat:write:customize権限が必要。

スコープの設定

ボット作成時にchat:writeのみ権限を与えている場合は追加する必要がある。

Slackアプリ一覧から対象の設定画面の「OAuth & Permissions」の設定を開き、「Bot Token Scopes」で「Add an OAuth Scope」押下し、chat:write:customizeを追加する。

追加すると画面上部に追加完了と、アプリのreinstallが必要な必要な旨が表示されるので、書かれているリンクに進んで更新した権限で再インストールする。

これで準備OK

パラメタの追加

  • アイコンはicon_urlにURLを設定。未指定の場合は https://docs.ansible.com/favicon.ico が使用される。
  • 名前はusernameに指定。未指定の場合は「Ansible」となる。

実装例

  - community.general.slack:
      token: "{{ oauth_token }}"
      msg: 完全にときめいちゃった!
      channel: develop
      color: '#1d1d1d'
      icon_url: https://lovelive-anime.jp/nijigasaki/img/tv/member/c01s.png
      username: '高咲侑'

この内容でタスクが実行されると以下の通り🌈

[AWS] VPCとS3エンドポイントを使ってインターネット接続が制限されたEC2(サブネット)からECRに接続する [修正済み]

6/23: S3へのエンドポイントも必要なことを追記

概要

インターネットゲートウェイを設定せずにインターネット接続が制限されたサブネットにあるEC2は、ECR(Elastic Container Registry)との疎通もないため、Dockerなどでコンテナイメージをpullしたりすることができない。

ここではVPCエンドポイントを使って、EC2(が接続されているの制限されたサブネット)とECRをプライベート接続して接続できるようにする設定をお試し。

ECRへ接続するための設定は以下の2つのサービスのエンドポイントを作成する。(エンドポイントはそれぞれ1つずつ作成する)
(6/23追記) ECRへ接続するための設定は以下の2つのサービスのVPCエンドポイントとS3ゲートウェイエンドポイントを作成する。(合計3つのエンドポイントを作成)

  • VPCエンドポイント
    • com.amazonaws.<リージョン名>.ecr.api
    • com.amazonaws.<リージョン名>.ecr.dkr
  • S3ゲートウェイエンドポイント
    • com.amazonaws.<リージョン名>.s3

なぜS3(へのアクセス)も必要かというと、ECRはAmazon S3を使用してイメージレイヤーを保存するため、pullの際はS3へのアクセスも発生する。

Amazon ECS タスクで Amazon ECR からプライベートイメージをプルするには、Amazon S3ゲートウェイエンドポイントを作成する必要があります。Amazon ECR は Amazon S3 を使用してイメージレイヤーを保存するため、ゲートウェイエンドポイントが必要です。Amazon ECR からイメージをダウンロードするコンテナは、Amazon ECR にアクセスしてイメージマニフェストを取得してから Amazon S3 にアクセスして実際のイメージレイヤーをダウンロードする必要があります

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/vpc-endpoints.html#ecr-setting-up-s3-gateway

webポータルから

VPCエンドポイントの作成

VPCmニューの「エンドポイント」から。
「エンドポイントを作成」して以下を入力する。
※ サービスにcom.amazonaws.<リージョン名>.ecr.apiを選択したものとcom.amazonaws.<リージョン名>.ecr.dkrを選択したものを1個ずつ作る

  • エンドポイントの設定
    • 名前タグ: 適当に入力 (apiとdkrがあるので、suffixにつけておくと良さげ)
    • サービスカテゴリ: AWSのサービス
  • サービス
    • サービス: com.amazonaws.<リージョン名>.ecr.apicom.amazonaws.<リージョン名>.ecr.dkr ※1個しか選択できないので2つ作る
  • VPC
    • VPC: 接続対象のVPC
    • 追加設定: デフォルトのまま
      • DNS名を有効
      • DNSレコードのIPタイプ: IPv4
  • サブネット
    • サブネット: 接続対象のAZとサブネットを選択
  • セキュリティグループ
    • 接続(インバウンド443/TCP)に必要なセキュリティグループを選択
  • ポリシー
    • デフォルトのまま (フルアクセス)

入力したら画面下部の「エンドポイントを作成」押下。 しばらく待てばステータスが「使用可能」になり、さらに少し時間を置けば、EC2からECRへ接続できるようになる。

[ec2-user@ip-10-1-2-229 ~]$ curl https://********.dkr.ecr.ap-northeast-1.amazonaws.com/container:tag
Not Authorized

※ 疎通確認用でDocker/Podman入ってないのでとりあえずcurlで疎通のみ確認。(未認証のレスポンスがあるのでHTTPSレベルで疎通できている)

VPCエンドポイントが無い状態だと以下の通り。

[ec2-user@ip-10-1-2-229 ~]$ curl https://********.dkr.ecr.ap-northeast-1.amazonaws.com/container:tag
curl: (7) Failed to connect to ********.dkr.ecr.ap-northeast-1.amazonaws.com port 443: Connection timed out

以下6/23追記

ただしこの時点ではS3への疎通はないため、レジストリへのログイン(podman login)は可能だけど、イメージpullは失敗する。

[ec2-user@ip-10-1-2-72 ~]$ podman login -u AWS ********.dkr.ecr.ap-northeast-1.amazonaws.com
Password: 
Login Succeeded!

[ec2-user@ip-10-1-2-72 ~]$ podman image pull ********.dkr.ecr.ap-northeast-1.amazonaws.com/redis:7
Trying to pull ********.dkr.ecr.ap-northeast-1.amazonaws.com/redis:7...
WARN[0030] Failed, retrying in 1s ... (1/3). Error: parsing image configuration: Get "https://prod-ap-northeast-1-starport-layer-bucket.s3.ap-northeast-1.amazonaws.com/ ... : dial tcp *.*.*.*:443: i/o timeout 

S3へのアクセスエラーはpublic ipが表示されている。

S3用ゲートウェイエンドポイントの作成

VPCエンドポイントの作成と同じ要領で、

  • サービスカテゴリ: AWSのサービス
  • サービス: gatewayでフィルターして、タイプが「Gateway」になっているcom.amazonaws.<リージョン名>.s3を選択
  • VPC: 接続対象のVPC
  • ルートテーブル: オフラインのサブネットが使うルートテーブルを選択

以上でエンドポイントを作成する。

これでECRに正しくアクセスできるようになる。

[ec2-user@ip-10-1-2-72 ~]$ podman image pull ********.dkr.ecr.ap-northeast-1.amazonaws.com/redis:7
Trying to pull ********.dkr.ecr.ap-northeast-1.amazonaws.com/redis:7...
Getting image source signatures
Copying blob 1adf6e0f68a8 done   | 
Copying blob 1adf6e0f68a8 done   | 
Copying blob e50738daf1e5 done   | 
:
:

Terraformで構築

上で操作した内容をそのままコードに落とし込めばOK
使うリソースはaws_vpc_endpoint

registry.terraform.io

VPCaws_vpc.example_dev_vpcで、サブネットはaws_subnet.example_dev_disconnected_subnetで作成済みで、リージョン名は変数でvar.region_nameにセットされてる構成。
また、ルートテーブルはVPCのデフォルトのものを使用。

resource "aws_vpc_endpoint" "example_dev_endpoint_to_ecr_api" {
  vpc_id = aws_vpc.example_dev_vpc.id
  service_name = "com.amazonaws.${var.region_name}.ecr.api"
  vpc_endpoint_type = "Interface"
  private_dns_enabled = true

  subnet_ids = [
    aws_subnet.example_dev_disconnected_subnet.id
  ]
  security_group_ids = [
    aws_security_group.example_dev_sg.id
  ]
  tags = {
    Name = "example-endpoint-ecr-api"
  }
}

resource "aws_vpc_endpoint" "example_dev_endpoint_to_ecr_dkr" {
  vpc_id = aws_vpc.example_dev_vpc.id
  service_name = "com.amazonaws.${var.region_name}.ecr.dkr"
  vpc_endpoint_type = "Interface"
  private_dns_enabled = true

  subnet_ids = [
    aws_subnet.example_dev_disconnected_subnet.id
  ]
  security_group_ids = [
    aws_security_group.example_dev_sg.id
  ]
  tags = {
    Name = "example-endpoint-ecr-dkr"
  }
}

resource "aws_vpc_endpoint" "example_dev_endpoint_to_s3" {
  vpc_id = aws_vpc.example_dev_vpc.id
  service_name = "com.amazonaws.${var.region_name}.s3"
  vpc_endpoint_type = "Gateway"

  # これはオフラインのサブネットはデフォルトのルートテーブルを使用してる場合
  route_table_ids = [aws_vpc.example_dev_vpc.default_route_table_id]

  tags = {
    Name = "example-endpoint-s3"
  }
}

参考

6/23追記

ECR用エンドポイントだけ作ってcurlHTTPS的にリポジトリと疎通確認してOKだと思い込んでたけど、実際にpodman image pullるとうまくいかず、S3のエンドポイント設定が必要なことに後から気づいた。
ドキュメントはちゃんと最後までよんで、最後までやりたい操作をきちんと実行して確認しましょう。

[Ansible / AAP / AWX] 通知機能を使ってジョブの実行状態をSlackへ通知する

AAP(Controller)とAWXには「通知」の機能の一つ(連携先の一つ)としてSlackにジョブの起動・終了(成功・失敗)等を通知することができる。
大まかな流れは以下の通り。

  1. Slackに投稿用botを用意しトークンを作成
  2. 作成したトークンを使ってAWX/AAPに通知リソース作成
  3. ジョブテンプレートに作成した通知リソースを設定

ここでは手動での各設定・リソース作成について紹介。

ちなみにAAPもAWXも同等。今回は以下の環境で確認。

  • AWX 24.4.0
  • AAP Controller 4.5.0

Slackに投稿用botを作成

api.slack.com

Slack Appsのページにアクセスし、[Create New App]押下

アプリの作成を手動かマニフェストファイル使用かを選択するので、ここは[From scratch]を選択。

「New app & choose workspace」画面で「App Name」にbot名(通知が行われる際にここで入力した名前でSlack投稿が行われる)と、対象ネームスペースを入力して[Create App]押下

権限設定

「Basic Information」画面に遷移するので、左側メニューの「OAuth & Permissions」を開いて画面中部の「Scopes -> Bot Token Scopes」にある[Add an OAuth Scope]ボタン押下し、選択肢から chat:write を選択。(calls:writeと間違えないこと)

ワークスペースbot追加

セットできたら同じ画面の最上部にスクロールして[Install to Workspace]押下し、ワークスペースへの権限リクエストを許可する。

これでSlackのワークスペースにアプリ(bot)が追加される。

Slack Appsのページには、設定用のトークンが表示されているのでコピーしておく。

チャンネルへbot追加

Slackアプリで作成したbotのユーザー詳細を開いてユーザー名押下し、「チャンネルにこのアプリを追加する」から通知を投稿するチャンネルを設定する。

AAPの通知設定

通知の作成

メニューの「管理 -> 通知」から。 [追加]を押下しタイプにSlackを選択し、追加の設定項目に以下を入力し保存。

  • チャネル名: 対象ワークスペース内の通知先チャンネル名 (文言は「チャネル」になってる?)
  • トークン: Slack Appsページで生成したトークン文字列
  • 通知の色: Slack投稿時のカラー設定。ライトピンクであれば#f0a6b8のように入力🎀

通知リソース作成後は[テスト]押下で投稿のテストができる。

Slackへ飛んだ通知テストはこの通り。

テンプレートへの通知設定

例としてデフォルトの「Demo Job Template」の場合、テンプレートの設定で「通知」タブを開き、通知を行いたいタイミングのチェックを有効にする。

この設定が入ってる状態でジョブを実行すると、Slackへジョブの開始と終了(成功or失敗)が通知されるようになる。

ワークフローテンプレートの場合はジョブテンプレートと同様のオプションに加えて、承認のタイミングでも通知を設定できる。

ポイントとしては「通知設定を行ったテンプレートのみで通知が行われる」ということ。
運用上、手動で実行するジョブは通知不要だけど、スケジュールで自動実行してる特定のジョブのみで通知を行いたい、みたいなことが可能。
(ジョブ単位の設定なので、1つのジョブに対して手動でも自動でも実行するけど、自動実行の時だけ通知、は無理…のはず)

URL設定

デフォルトだと通知されたときのURLがhttps://towerhostとなっており、Slackのリンクからwebページへ遷移することができない。
これを正しく設定するには「設定 -> その他のシステム設定」から、「サービスのベースURL」を編集する。

これで設定したURLベースで表示されるようになる。


今回は手動のリソース作成について手順をまとめたが、プレイブックとモジュールを使ったリソース作成も可能。ただし「通知のテスト」に限っては(APIはあるけど)プレイブック用のモジュールは現状無い(uriモジュールでRESTを直接叩けばテスト可能)
そのあたりの構築自動化はまた別途…

また、通知のタイプはいろいろ用意されており例としてSlackを取り上げたが、例えばオンプレなどでSlack(というかインターネット)への接続に制限があるような環境であれば、他にはSMTPや、Webhookで作りこむこともできる。


ジョブの実行ログをリモート転送するなら以下

zaki-hmkc.hatenablog.com

コンテナ化されたAAPのContainerized Ansible Automation Platform (tech preview)を試してみた(AAP 2.4)

最近AAPのインストーラーの新しいバージョンをダウンロードしようとしたら、見慣れない「Tech Preview: Ansible Automation Platform 2.4 Containerized Setup」というインストーラが(日付を見る限り1月の時点ですでに)あったので試してみた。

access.redhat.com

日本語ドキュメントを見ると「コンテナー化されたAnsible Automation Platform」とあるけど、インストール手順メインで具体的にどんな違いがあるかさらっと見た感じだとわからなかったので実際に試してみた。
いれただけレベルだけど、コンテナ環境で動くAAPかな?と思いつつ入れてみたら、だいたいそんな感じだった。

試したのはBundle版のインストーラ。(インストール時に追加パッケージをダウンロードせずに、インストーラが全部入りになってるタイプ)

ホストの準備

RHEL9.2ベースのホストと書かれてるけど、OSを更新すると9.4になるのでこれで進める。

[zaki@rhel9-dev2 ~]$ cat /etc/redhat-release 
Red Hat Enterprise Linux release 9.4 (Plow)

システム要件通り4CPU、16GBメモリ、ストレージはケチって20GBで構築。
ちなみにDBとControllerのみの構築で10GB弱になる。

DNFリポジトリ

BaseOS と appstream リポジトリーのみがホスト上でセットアップされていること。

[zaki@rhel9-dev2 ~]$ dnf repolist
Not root, Subscription Management repositories not updated
repo id                                     repo name
rhel-9-for-x86_64-appstream-rpms            Red Hat Enterprise Linux 9 for x86_64 - AppStream (RPMs)
rhel-9-for-x86_64-baseos-rpms               Red Hat Enterprise Linux 9 for x86_64 - BaseOS (RPMs)
[zaki@rhel9-dev2 ~]$ 

DNS

DNSが設定しており、FQDNで名前解決できること。

zaki@cloud-dev2:~$ dig @192.168.0.4 rhel9-dev2.naru.jp-z.jp
...

;; ANSWER SECTION:
rhel9-dev2.naru.jp-z.jp. 0      IN      A       192.168.0.80

ansible-coreのインストール

[zaki@rhel9-dev2 ~]$ sudo dnf install ansible-core wget git rsync
Updating Subscription Management repositories.
Last metadata expiration check: 2:17:01 ago on Sun Jun  9 09:53:56 2024.
Package rsync-3.2.3-19.el9.x86_64 is already installed.
Dependencies resolved.
===============================================================================================================
 Package                     Arch        Version                   Repository                             Size
===============================================================================================================
Installing:
 ansible-core                x86_64      1:2.14.14-1.el9           rhel-9-for-x86_64-appstream-rpms      2.6 M
 git                         x86_64      2.43.0-1.el9              rhel-9-for-x86_64-appstream-rpms       54 k
 wget                        x86_64      1.21.1-7.el9              rhel-9-for-x86_64-appstream-rpms      794 k
Installing dependencies:
 emacs-filesystem            noarch      1:27.2-9.el9              rhel-9-for-x86_64-appstream-rpms      9.6 k
 git-core                    x86_64      2.43.0-1.el9              rhel-9-for-x86_64-appstream-rpms      4.4 M
 git-core-doc                noarch      2.43.0-1.el9              rhel-9-for-x86_64-appstream-rpms      2.9 M
 perl-AutoLoader             noarch      5.74-481.el9              rhel-9-for-x86_64-appstream-rpms       21 k
 perl-B                      x86_64      1.80-481.el9              rhel-9-for-x86_64-appstream-rpms      184 k
 perl-Carp                   noarch      1.50-460.el9              rhel-9-for-x86_64-appstream-rpms       31 k
 perl-Class-Struct           noarch      0.66-481.el9              rhel-9-for-x86_64-appstream-rpms       22 k
 perl-Data-Dumper            x86_64      2.174-462.el9             rhel-9-for-x86_64-appstream-rpms       59 k
 perl-Digest                 noarch      1.19-4.el9                rhel-9-for-x86_64-appstream-rpms       29 k
 perl-Digest-MD5             x86_64      2.58-4.el9                rhel-9-for-x86_64-appstream-rpms       39 k
 perl-DynaLoader             x86_64      1.47-481.el9              rhel-9-for-x86_64-appstream-rpms       26 k
 perl-Encode                 x86_64      4:3.08-462.el9            rhel-9-for-x86_64-appstream-rpms      1.7 M
 perl-Errno                  x86_64      1.30-481.el9              rhel-9-for-x86_64-appstream-rpms       15 k
 perl-Error                  noarch      1:0.17029-7.el9           rhel-9-for-x86_64-appstream-rpms       46 k
 perl-Exporter               noarch      5.74-461.el9              rhel-9-for-x86_64-appstream-rpms       34 k
 perl-Fcntl                  x86_64      1.13-481.el9              rhel-9-for-x86_64-appstream-rpms       22 k
 perl-File-Basename          noarch      2.85-481.el9              rhel-9-for-x86_64-appstream-rpms       17 k
 perl-File-Find              noarch      1.37-481.el9              rhel-9-for-x86_64-appstream-rpms       26 k
 perl-File-Path              noarch      2.18-4.el9                rhel-9-for-x86_64-appstream-rpms       38 k
 perl-File-Temp              noarch      1:0.231.100-4.el9         rhel-9-for-x86_64-appstream-rpms       63 k
 perl-File-stat              noarch      1.09-481.el9              rhel-9-for-x86_64-appstream-rpms       17 k
 perl-FileHandle             noarch      2.03-481.el9              rhel-9-for-x86_64-appstream-rpms       16 k
 perl-Getopt-Long            noarch      1:2.52-4.el9              rhel-9-for-x86_64-appstream-rpms       64 k
 perl-Getopt-Std             noarch      1.12-481.el9              rhel-9-for-x86_64-appstream-rpms       16 k
 perl-Git                    noarch      2.43.0-1.el9              rhel-9-for-x86_64-appstream-rpms       39 k
 perl-HTTP-Tiny              noarch      0.076-462.el9             rhel-9-for-x86_64-appstream-rpms       57 k
 perl-IO                     x86_64      1.43-481.el9              rhel-9-for-x86_64-appstream-rpms       92 k
 perl-IO-Socket-IP           noarch      0.41-5.el9                rhel-9-for-x86_64-appstream-rpms       45 k
 perl-IO-Socket-SSL          noarch      2.073-1.el9               rhel-9-for-x86_64-appstream-rpms      223 k
 perl-IPC-Open3              noarch      1.21-481.el9              rhel-9-for-x86_64-appstream-rpms       24 k
 perl-MIME-Base64            x86_64      3.16-4.el9                rhel-9-for-x86_64-appstream-rpms       34 k
 perl-Mozilla-CA             noarch      20200520-6.el9            rhel-9-for-x86_64-appstream-rpms       14 k
 perl-Net-SSLeay             x86_64      1.92-2.el9                rhel-9-for-x86_64-appstream-rpms      392 k
 perl-POSIX                  x86_64      1.94-481.el9              rhel-9-for-x86_64-appstream-rpms       98 k
 perl-PathTools              x86_64      3.78-461.el9              rhel-9-for-x86_64-appstream-rpms       92 k
 perl-Pod-Escapes            noarch      1:1.07-460.el9            rhel-9-for-x86_64-appstream-rpms       22 k
 perl-Pod-Perldoc            noarch      3.28.01-461.el9           rhel-9-for-x86_64-appstream-rpms       92 k
 perl-Pod-Simple             noarch      1:3.42-4.el9              rhel-9-for-x86_64-appstream-rpms      229 k
 perl-Pod-Usage              noarch      4:2.01-4.el9              rhel-9-for-x86_64-appstream-rpms       43 k
 perl-Scalar-List-Utils      x86_64      4:1.56-461.el9            rhel-9-for-x86_64-appstream-rpms       77 k
 perl-SelectSaver            noarch      1.02-481.el9              rhel-9-for-x86_64-appstream-rpms       12 k
 perl-Socket                 x86_64      4:2.031-4.el9             rhel-9-for-x86_64-appstream-rpms       58 k
 perl-Storable               x86_64      1:3.21-460.el9            rhel-9-for-x86_64-appstream-rpms       98 k
 perl-Symbol                 noarch      1.08-481.el9              rhel-9-for-x86_64-appstream-rpms       14 k
 perl-Term-ANSIColor         noarch      5.01-461.el9              rhel-9-for-x86_64-appstream-rpms       51 k
 perl-Term-Cap               noarch      1.17-460.el9              rhel-9-for-x86_64-appstream-rpms       24 k
 perl-TermReadKey            x86_64      2.38-11.el9               rhel-9-for-x86_64-appstream-rpms       40 k
 perl-Text-ParseWords        noarch      3.30-460.el9              rhel-9-for-x86_64-appstream-rpms       18 k
 perl-Text-Tabs+Wrap         noarch      2013.0523-460.el9         rhel-9-for-x86_64-appstream-rpms       25 k
 perl-Time-Local             noarch      2:1.300-7.el9             rhel-9-for-x86_64-appstream-rpms       37 k
 perl-URI                    noarch      5.09-3.el9                rhel-9-for-x86_64-appstream-rpms      125 k
 perl-base                   noarch      2.27-481.el9              rhel-9-for-x86_64-appstream-rpms       16 k
 perl-constant               noarch      1.33-461.el9              rhel-9-for-x86_64-appstream-rpms       25 k
 perl-if                     noarch      0.60.800-481.el9          rhel-9-for-x86_64-appstream-rpms       14 k
 perl-interpreter            x86_64      4:5.32.1-481.el9          rhel-9-for-x86_64-appstream-rpms       73 k
 perl-lib                    x86_64      0.65-481.el9              rhel-9-for-x86_64-appstream-rpms       15 k
 perl-libnet                 noarch      3.13-4.el9                rhel-9-for-x86_64-appstream-rpms      134 k
 perl-libs                   x86_64      4:5.32.1-481.el9          rhel-9-for-x86_64-appstream-rpms      2.2 M
 perl-mro                    x86_64      1.23-481.el9              rhel-9-for-x86_64-appstream-rpms       29 k
 perl-overload               noarch      1.31-481.el9              rhel-9-for-x86_64-appstream-rpms       46 k
 perl-overloading            noarch      0.02-481.el9              rhel-9-for-x86_64-appstream-rpms       13 k
 perl-parent                 noarch      1:0.238-460.el9           rhel-9-for-x86_64-appstream-rpms       16 k
 perl-podlators              noarch      1:4.14-460.el9            rhel-9-for-x86_64-appstream-rpms      118 k
 perl-subs                   noarch      1.03-481.el9              rhel-9-for-x86_64-appstream-rpms       12 k
 perl-vars                   noarch      1.05-481.el9              rhel-9-for-x86_64-appstream-rpms       13 k
 python3-cffi                x86_64      1.14.5-5.el9              rhel-9-for-x86_64-appstream-rpms      257 k
 python3-cryptography        x86_64      36.0.1-4.el9              rhel-9-for-x86_64-baseos-rpms         1.2 M
 python3-packaging           noarch      20.9-5.el9                rhel-9-for-x86_64-appstream-rpms       81 k
 python3-ply                 noarch      3.11-14.el9               rhel-9-for-x86_64-appstream-rpms      111 k
 python3-pycparser           noarch      2.20-6.el9                rhel-9-for-x86_64-appstream-rpms      139 k
 python3-pyparsing           noarch      2.4.7-9.el9               rhel-9-for-x86_64-baseos-rpms         154 k
 python3-resolvelib          noarch      0.5.4-5.el9               rhel-9-for-x86_64-appstream-rpms       38 k
 sshpass                     x86_64      1.09-4.el9                rhel-9-for-x86_64-appstream-rpms       30 k
Installing weak dependencies:
 perl-NDBM_File              x86_64      1.15-481.el9              rhel-9-for-x86_64-appstream-rpms       23 k

Transaction Summary
===============================================================================================================
Install  77 Packages

Total download size: 20 M
Installed size: 84 M
Is this ok [y/N]: y
Downloading Packages:
(1/77): perl-Data-Dumper-2.174-462.el9.x86_64.rpm                               95 kB/s |  59 kB     00:00    
(2/77): perl-Digest-MD5-2.58-4.el9.x86_64.rpm                                   63 kB/s |  39 kB     00:00    
(3/77): perl-Error-0.17029-7.el9.noarch.rpm                                    217 kB/s |  46 kB     00:00 

[...]

ホスト名の設定がこのセクションにあるのは微妙な気もするけどとりあえず手順通りに。

[zaki@rhel9-dev2 ~]$ sudo hostnamectl set-hostname rhel9-dev2.naru.jp-z.jp
[zaki@rhel9-dev2 ~]$ hostname
rhel9-dev2.naru.jp-z.jp

インストーラの準備

[zaki@rhel9-dev2 ~]$ sha256sum /mnt/template/iso/ansible-automation-platform-containerized-setup-bundle-2.4-2-x
86_64.tar.gz 
0781cab21cd21992a22bb8ed6f9b1018dff0d092d5882e1e9aafbd48337f6662  /mnt/template/iso/ansible-automation-platform-containerized-setup-bundle-2.4-2-x86_64.tar.gz
[zaki@rhel9-dev2 ~]$ tar xf /mnt/template/iso/ansible-automation-platform-containerized-setup-bundle-2.4-2-x86_64.tar.gz 
[zaki@rhel9-dev2 ~]$
[zaki@rhel9-dev2 ~]$ cd ansible-automation-platform-containerized-setup-bundle-2.4-2-x86_64/
[zaki@rhel9-dev2 ansible-automation-platform-containerized-setup-bundle-2.4-2-x86_64]$ ls -F
README.md  ansible.cfg  bundle/  collections/  inventory

Containerized AAPのインストール

とりあえずお試しなのでAutomation Controllerのみの構成。

インベントリファイルの編集

「実験的なポストインストーラー機能」についての記載があるが、何が可能になってるのか詳細がよくわからないのと、ライセンスファイルが必要で個人の開発者サブスクリプションではたぶん用意できないのでスルー。 (containerized版でない従来のAAPインストーラーにはこのパラメタはなかった)

注意点として、既存のAAPは「特にさわらなければ推奨のPostgreSQLが自動でローカルにインストールされる」という動きになるが、コンテナ版はDBの設定が入っていない(コメントアウトされてる)ので、明確に記述の必要がある。
特に[database]コメントアウトを解除するのを忘れずに。(訳:見落としてハマった)

逆にAutomation HubやEvent-Driven Ansibleはホスト名を記入するような書式になってるので、使わないなら削除する。

また、Bundle版を使用する場合はbundle_installの有効化とbundle_dirアーカイブを展開した中にあるbundleディレクトリを指定する必要がある。

オリジナルのファイルからの変更点は手元の環境では以下の通り。

$ diff -U1 inventory.org i
nventory
--- inventory.org       2024-01-03 06:01:01.000000000 +0900
+++ inventory   2024-06-09 16:18:31.684723915 +0900
@@ -7,3 +7,3 @@
 [automationcontroller]
-fqdn_of_your_rhel_host ansible_connection=local
+rhel9-dev2.naru.jp-z.jp ansible_connection=local
 
@@ -12,3 +12,2 @@
 [automationhub]
-fqdn_of_your_rhel_host ansible_connection=local
 
@@ -17,3 +16,2 @@
 [automationeda]
-fqdn_of_your_rhel_host ansible_connection=local
 
@@ -29,4 +27,4 @@
 # see mandatory sections under each AAP component
-#[database]
-#fqdn_of_your_rhel_host ansible_connection=local
+[database]
+rhel9-dev2.naru.jp-z.jp ansible_connection=local
 
@@ -37,3 +35,3 @@
 postgresql_admin_username=postgres
-postgresql_admin_password=<set your own>
+postgresql_admin_password=awx
 # If using the online (non-bundled) installer, you need to set RHN registry credentials
@@ -42,5 +40,5 @@
 # If using the bundled installer, you need to alter defaults by using:
-#bundle_install=true
+bundle_install=true
 # The bundle directory must include /bundle in the path
-#bundle_dir=<full path to the bundle directory>
+bundle_dir=/home/zaki/ansible-automation-platform-containerized-setup-bundle-2.4-2-x86_64/bundle
 # To add more decision environment images you need to set the de_extra_images variable
@@ -61,5 +59,5 @@
 # --------------------------
-controller_admin_password=<set your own>
-controller_pg_host=fqdn_of_your_rhel_host
-controller_pg_password=<set your own>
+controller_admin_password=curry_tabetai
+controller_pg_host=rhel9-dev2.naru.jp-z.jp
+controller_pg_password=awx

インストール

通常のAAPと異なり、ansible-playbookを直接実行する。

$ ansible-playbook -i inventory ansible.containerized_installer.install

手元の環境では5分半程度。速い。

PLAY RECAP ****************************************************************************************************
localhost                  : ok=12   changed=0    unreachable=0    failed=0    skipped=19   rescued=0    ignored=0   
rhel9-dev2.naru.jp-z.jp    : ok=190  changed=89   unreachable=0    failed=0    skipped=52   rescued=0    ignored=0   


real    5m27.918s
user    1m59.802s
sys     0m45.173s

実行状態

Web UI

ブラウザで8443/TCPにアクセス(HTTPS)すれば見覚えのある画面が表示される。

初回起動時にサブスクリプションの設定があるなど、この辺は通常のAAPと同じ。

Podmanの状態

rootやawxユーザーでなく、インストールしたユーザー権限で動作している。(だから標準のポートが8443/tcpだったのかな)

[zaki@rhel9-dev2 ~]$ podman ps
CONTAINER ID  IMAGE                                                                        COMMAND               CREATED        STATUS        PORTS       NAMES
335ccc25ca2b  registry.redhat.io/rhel8/postgresql-13:latest                                run-postgresql        6 minutes ago  Up 6 minutes              postgresql
abaef3f6a02b  registry.redhat.io/rhel8/redis-6:latest                                      run-redis             6 minutes ago  Up 6 minutes              redis
ebecd1131288  registry.redhat.io/ansible-automation-platform-24/ee-supported-rhel8:latest  /usr/bin/receptor...  6 minutes ago  Up 6 minutes              receptor
9e7c3c01e270  registry.redhat.io/ansible-automation-platform-24/controller-rhel8:latest    /usr/bin/launch_a...  6 minutes ago  Up 4 minutes              automation-controller-rsyslog
9e28a45ee416  registry.redhat.io/ansible-automation-platform-24/controller-rhel8:latest    /usr/bin/launch_a...  5 minutes ago  Up 4 minutes              automation-controller-task
72656085363f  registry.redhat.io/ansible-automation-platform-24/controller-rhel8:latest    /usr/bin/launch_a...  5 minutes ago  Up 3 minutes              automation-controller-web

task/web/rsyslog/receptorと、雰囲気的にはAWXの構成に近付い模様。

ちなみに、ジョブ実行時のコンテナがどこで動いてるかはわからなかった。(podman psを連打してても、ジョブ実行用のコンテナが起動してる感じがしなかった)
ジョブ実行のコンテナは、receptorコンテナの中で実行されているのを(ジョブ実行のタイミングでpodman psを連打して)確認。いわゆるpodman in podman構成で、ホストOSからは直接コンテナは見えない。

[zaki@rhel9-dev2 ~]$ podman exec -it receptor bash
bash-4.4$ 
bash-4.4$ ansible --version
ansible [core 2.15.8]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/runner/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /home/runner/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.9.18 (main, Sep 22 2023, 17:58:34) [GCC 8.5.0 20210514 (Red Hat 8.5.0-20)] (/usr/bin/python3.9)
  jinja version = 3.1.2
  libyaml = True
bash-4.4$
bash-4.4$
bash-4.4$ podman ps -a
CONTAINER ID  IMAGE                                                                      COMMAND               CREATED                 STATUS                 PORTS       NAMES
2994ea27cff0  registry.redhat.io/ansible-automation-platform-24/ee-minimal-rhel8:latest  ansible-playbook ...  Less than a second ago  Up Less than a second              ansible_runner_7
bash-4.4$

taskのコンテナのログを確認すれば、ジョブが起動していることを確認できる。

[zaki@rhel9-dev2 ~]$ podman logs automation-controller-task
[wait-for-migrations] Waiting for database migrations...
[wait-for-migrations] Attempt 1
2024-06-09 07:24:19,342 INFO RPC interface 'supervisor' initialized
2024-06-09 07:24:19,342 INFO RPC interface 'supervisor' initialized
2024-06-09 07:24:19,342 CRIT Server 'unix_http_server' running without any HTTP authentication checking
2024-06-09 07:24:19,342 CRIT Server 'unix_http_server' running without any HTTP authentication checking
2024-06-09 07:24:19,342 INFO supervisord started with pid 2

[...]

2024-06-09 07:33:28,644 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 controller node chosen
2024-06-09 07:33:28,644 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 execution node chosen
2024-06-09 07:33:28,673 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 waiting
2024-06-09 07:33:28,768 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 pre run
2024-06-09 07:33:28,807 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 preparing playbook
2024-06-09 07:33:28,857 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 running playbook
2024-06-09 07:33:28,866 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 work unit id received
2024-06-09 07:33:28,926 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 work unit id assigned
2024-06-09 07:33:31,436 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.main.commands.run_callback_receiver Starting EOF event processing for Job 5
2024-06-09 07:33:31,438 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 post run
2024-06-09 07:33:31,511 INFO     [0e45219bc6404f0f8601e7f1da2d2fd7] awx.analytics.job_lifecycle job-5 finalize run
2024-06-09 07:33:32,046 INFO     [-] awx.analytics.job_lifecycle job-5 stats wrapup finished

awxユーザーはホストOS上には存在せず、コンテナ内の実行ユーザーとして一応いる。

[zaki@rhel9-dev2 ~]$ podman exec -it automation-controller-task id
uid=1000(awx) gid=0(root) groups=0(root)

雑感

インストールしてみただけなので使い勝手とかはコメントしづらいけど多分従来のAAPと同様なのかな。
今までは様々なコンポーネントをインストールする必要があったけど、コンテナをデプロイする形式になったのでかなり高速化された印象。

インストール手順にAnsibleのインストールがあるけど、おそらくインストールに使用するためのAnsibleなので、AAP本体やジョブとは独立してると思われ。

まだ試してはないけど、Hubなど追加コンポーネントもインストールするのであれば、ホストグループに加えてHub用のPostgreSQLの情報を追加する。

# This section is for your AAP Automation Hub host(s)
# -----------------------------------------------------
[automationhub]
rhel9-dev2.naru.jp-z.jp ansible_connection=local

:

# AAP Automation Hub - mandatory
# ------------------------------
hub_admin_password=curry_tabetai
hub_pg_host=rhel9-dev2.naru.jp-z.jp
hub_pg_password=awx

あとインストール時の引数のansible.containerized_installer.install、そんなplaybookファイルないけど何だろうと思って実行したら、ansible.containerized_installerが内蔵のコレクション名っぽい。プラグイン名を指定してもansible-playbookって実行できるんだね。

$ ansible-playbook -i inventory ansible.containerized_installer.install
[WARNING]: running playbook inside collection ansible.containerized_installer

AWXのバックアップ・リストアが付属のpostgresql-15-c9s:latestでうまくいかないときのワークアラウンド

原因

現在標準のquay.io/sclorg/postgresql-15-c9s:latestはUID 26で動くので、「UID 26のユーザーがRead/Writeできない領域のボリュームへのバックアップ」が失敗する。
例えば↓のような「rootか特定のUIDでないと書き込めないNFSサーバー領域」とかね。

root@sample-app-85c7f885b4-65n46:~# ls -al /mnt/
total 28
drwxr-xr-x 7 1000 1000 4096 May 28 14:32 .
drwxr-xr-x 1 root root 4096 May 28 13:41 ..
drwxr-xr-x 2 root root 4096 May 27 08:31 tower-openshift-backup-2024-05-27-083135
drwxr-xr-x 2 root root 4096 May 28 12:48 tower-openshift-backup-2024-05-28-124841
drwxr-xr-x 2 root root 4096 May 28 13:06 tower-openshift-backup-2024-05-28-130641
drwxr-xr-x 2 root root 4096 May 28 13:19 tower-openshift-backup-2024-05-28-131910
drwxr-xr-x 2 root root 4096 May 28 13:48 tower-openshift-backup-2024-05-28-134803

回避策

恒久対策されるまではバックアップとリストアの際は、ファイルシステム側をUID 26で読み書きできるようにするか、UID 0で動作するdocker.ioで公開されているpostgres:15を使用(v2.11くらいまでの従来の構成)すればOK

バックアップ/リストアPodでDocker Hubのpostgres:15の指定

postgres:15を使う場合は、AWXBackupとAWXRestoreリソースに(ドキュメントには記載はないけど)postgres_imagepostgres_image_versionでイメージとタグを指定する。

---
apiVersion: awx.ansible.com/v1beta1
kind: AWXBackup
metadata:
  name: awxbackup-2024-05-28-04
  namespace: awx
spec:
  deployment_name: awx
  backup_pvc: nfs-pv01-claim
  postgres_image: postgres        # これ
  postgres_image_version: "15"    # これ
---
apiVersion: awx.ansible.com/v1beta1
kind: AWXRestore
metadata:
  name: awxrestore
  namespace: awx
spec:
  deployment_name: awx
  backup_name: awxbackup-2024-05-28-04
  postgres_image: postgres        # これ
  postgres_image_version: "15"    # これ

関連情報

調査中に「issueを検索する」まで頭が回らなかったけど、関連情報は以下

github.com

github.com

github.com

バックアップ・リストアのドキュメントは現状以下。

github.com

github.com

バックアップ時のログ

バックアップ・リストア用のPodはログを出力しないのでkubectl logsを実行してもログを確認できない。
ログを確認するには、OperatorのPodのログを確認すればバックアップ・リストアのためのAnsible Playbookを実行しているログを確認できる。

PVへ書き込み権限が無い場合は下記エラーが出力されている。

--------------------------- Ansible Task StdOut -------------------------------

 TASK [Create directory for backup] ******************************** 
fatal: [localhost]: FAILED! => {"changed": true, "rc": 1, "return_code": 1, "stderr": "mkdir: cannot create directory '/backups/tower-openshift-backup-2024-05-28-135907': Permission denied\n", "stderr_lines": ["mkdir: cannot create directory '/backups/tower-openshift-backup-2024-05-28-135907': Permission denied"], "stdout": "", "stdout_lines": []}

確認バージョン

  • AWX Operator: 2.17.0
  • AWX: 24.4.0

※ DBにquay.io/sclorg/postgresql-15-c9s:latestを使用しているバージョンは(修正されるまでは)対象になるはず。おそらくOperator 2.12か2.13以降全て。

オフラインK3sクラスタでworkerノード追加

少し前に実施したシングルノード構成の「オフライン環境へのK3sインストール」に、workerノードを追加する。
環境は引き続きインターネット接続のないオフライン環境。

zaki-hmkc.hatenablog.com

手順はAir-Gap Installの通りで大丈夫。

docs.k3s.io

構成 (再掲)

  • ホストOS: Ubuntu 22.04.03 LTS
  • K3s: v1.29.2
  • プライベートコンテナレジストリ: ローカルのGitLab使用
  • ネットワークはcontrol planeと同じサブネットにいて、control planeともプライベートレジストリとも直接疎通がある構成 (インターネットには接続できない)

root@k3s-offline2:~# ip r
default via 172.29.0.1 dev eth0 proto static 
172.29.0.0/24 dev eth0 proto kernel scope link src 172.29.0.78

control planeは以下

root@k3s-offline1:~# ip r
default via 172.29.0.1 dev eth0 proto static 
10.42.0.0/24 dev cni0 proto kernel scope link src 10.42.0.1 
172.29.0.0/24 dev eth0 proto kernel scope link src 172.29.0.77 

イメージの準備

これはcontrol planeを構築したときのものをそのまま使用。

プライベートレジストリのリダイレクト設定

これはcontrol planeノード同様に各ノードで実施。
以下の内容の/etc/rancher/k3s/registries.yamlを作成する。

mirrors:
  docker.io:
    endpoint:
      - "https://gitlab.example.jp:25000"
    rewrite:
      "^rancher/(.*)": "zaki/images/$1"
configs:
  "gitlab.example.jp:25000":
    tls:
      insecure_skip_verify: true

インストールスクリプトの配置

これもcontrol plane構築のものをそのまま使用。
構築対象ノードのどこかに配置しておく。
workerの台数が多い場合はIPリーチするwebサーバーを立てるのが楽かもしれない。(が、今回はローカル配置で実施)

https://get.k3s.io

実行バイナリの配置

同じくcontrol plane構築の(ry
追加するノードの/usr/local/bin/k3sへ配置。

zaki@k3s-offline2:~$ sudo cp k3s /usr/local/bin/k3s 
zaki@k3s-offline2:~$ sudo chmod 755 /usr/local/bin/k3s
zaki@k3s-offline2:~$ k3s --version
k3s version v1.29.2+k3s1 (86f10213)
go version go1.21.7

トークン確認

クラスターへ参加するためのトークンをcontrol planeノードで確認。
ここはオンラインの場合の手順と同様。

root@k3s-offline1:~# cat /var/lib/rancher/k3s/server/token

内容をworkerノードで環境変数あたりにセットしておく。

インストール

イメージ、バイナリ、インストールスクリプトトークンの準備ができたら、以下のオプションを付与してインストールスクリプトを実行する。

  • INSTALL_K3S_SKIP_DOWNLOAD=true (ダウンロード処理をskip / control planeと同様)
  • K3S_URL=control planeのアドレス (構築済みcontrol planeを指定 / オンライン時の手順と同様)
  • K3S_TOKEN=上記で確認したトークン値 (クラスター参加用トークン / オンライン時の手順と同様)
zaki@k3s-offline2:~$ INSTALL_K3S_SKIP_DOWNLOAD=true K3S_URL=https://172.29.0.77:6443 K3S_TOKEN=${K3S_TOKEN} ./install.sh 
[INFO]  Skipping k3s download and verify
[INFO]  Skipping installation of SELinux RPM
[INFO]  Creating /usr/local/bin/kubectl symlink to k3s
[INFO]  Creating /usr/local/bin/crictl symlink to k3s
[INFO]  Creating /usr/local/bin/ctr symlink to k3s
[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
[INFO]  Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh
[INFO]  env: Creating environment file /etc/systemd/system/k3s-agent.service.env
[INFO]  systemd: Creating service file /etc/systemd/system/k3s-agent.service
[INFO]  systemd: Enabling k3s-agent unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s-agent.service → /etc/systemd/system/k3s-agent.service.
[INFO]  systemd: Starting k3s-agent

比較的すぐ完了し、get nodeでノードが追加されることを確認できる。

root@k3s-offline1:~# kubectl get node
NAME           STATUS   ROLES                  AGE   VERSION
k3s-offline1   Ready    control-plane,master   50d   v1.29.2+k3s1
k3s-offline2   Ready    <none>                 4s    v1.29.2+k3s1

root@k3s-offline1:~# kubectl get pod -A -o wide
NAMESPACE     NAME                                      READY   STATUS      RESTARTS       AGE    IP           NODE           NOMINATED NODE   READINESS GATES
kube-system   helm-install-traefik-crd-xsblz            0/1     Completed   0              50d    <none>       k3s-offline1   <none>           <none>
kube-system   helm-install-traefik-gvq48                0/1     Completed   1              50d    <none>       k3s-offline1   <none>           <none>
kube-system   svclb-sample-http-4cda7872-mdm9q          1/1     Running     6 (25h ago)    50d    10.42.0.58   k3s-offline1   <none>           <none>
kube-system   svclb-traefik-10a1b448-6h6c9              2/2     Running     12 (25h ago)   50d    10.42.0.55   k3s-offline1   <none>           <none>
kube-system   traefik-f4564c4f4-gg6mv                   1/1     Running     6 (25h ago)    50d    10.42.0.62   k3s-offline1   <none>           <none>
kube-system   coredns-6799fbcd5-vvxrq                   1/1     Running     4 (25h ago)    46d    10.42.0.59   k3s-offline1   <none>           <none>
kube-system   local-path-provisioner-6c86858495-qznr7   1/1     Running     12 (25h ago)   50d    10.42.0.61   k3s-offline1   <none>           <none>
kube-system   metrics-server-67c658944b-r655h           1/1     Running     12 (25h ago)   50d    10.42.0.60   k3s-offline1   <none>           <none>
sample-app    sample-http-5cd4944c69-6knfd              1/1     Running     2 (46d ago)    50d    10.42.0.56   k3s-offline1   <none>           <none>
sample-app    sample-http-5cd4944c69-l7xnb              1/1     Running     2 (46d ago)    50d    10.42.0.57   k3s-offline1   <none>           <none>
kube-system   svclb-traefik-10a1b448-8vhz9              2/2     Running     0              4m5s   10.42.1.3    k3s-offline2   <none>           <none>
kube-system   svclb-sample-http-4cda7872-f6jzs          1/1     Running     0              4m5s   10.42.1.2    k3s-offline2   <none>           <none>

「オフライン構築」と「workerノードの追加」をミックスした手順なので、それぞれ実施経験があれば簡単に構築できる。