zaki work log

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

[Ansible / Docker / K3s] AWX 20.0.0をarm64環境でビルド&デプロイするplaybook

AWXはamd64向けのイメージしか提供されてないので、Oracle CloudのAlways Free枠で使用できる4コア・RAM24GBのA1.FlexインスタンスのようなARMアーキテクチャの環境ではそのままでは利用できません。
イメージをARM環境でリビルドしてやれば動作するので、さくっと構築できるようにplaybookを作ったのでその内容についてメモしました。

ビルド済みarm64イメージのデプロイをちょっと試す

私がビルドしたarm64のイメージをDockerHubとGitHub Container Registryで公開しています。
このイメージをデプロイするだけで良いのであれば、arm64環境で以下のコマンドでデプロイできます。

$ git clone -b 0.17.0 https://github.com/ansible/awx-operator.git
$ cd awx-operator/
$ export NAMESPACE=awx
$ make deploy IMG=zakihmkc/awx-operator:0.17.0

※ 記事作成時点で最新は0.18.0だけど、operatorのデプロイ後カスタムリソースからpodが何故か作成されなかったので、動作確認できてる0.17.0を使ってます。

awx-operatorがデプロイされたら、以下の内容のAWXカスタムリソースをデプロイ。

---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx-demo
  namespace: awx
spec:
  service_type: nodeport
  image: zakihmkc/awx
  image_version: 20.0.0
  image_pull_policy: Always
  ee_images:
    - name: awx-ee-arm64
      image: zakihmkc/awx-ee:0.6.0
    - name: AWX EE (latest)
      image: zakihmkc/awx-ee:0.6.0
  control_plane_ee_image: zakihmkc/awx-ee:0.6.0

ノードのCPUやメモリリソースが少なくてpodがpendingになる場合は、Containers Resource Requirementsのパラメタを追加してrequestsの制限を入れてください。

AWXをビルド・デプロイするplaybook

Oracle CloudのA1.FlexシェイプのUbuntu 20.04 (4コア・RAM24GB)で動作確認しています。
デプロイ直後のまっさらな状態のUbuntu 20.04インスタンスに対して、Ansibleを使って、Dockerのインストールとイメージのbuildとpushを行い、K3sのインストールとAWXのデプロイを行います。外部アクセス用には、K3s標準のTraefikのIngressを使用します。

github.com

使い方

リポジトリのクローン

$ git clone https://github.com/zaki-lknr/awx_build_and_deploy.git
$ cd awx_build_and_deploy

インベントリ

inventory-example.iniで定義されている変数は以下の通り。
ビルドしたイメージは、registry_urlregistry_usernameを元にレジストリURLを生成しpushされます。
ビルドは行わずに、前述手動の手順と同様にビルド済みのイメージをデプロイするだけであれば、buildホストグループとregistry_*コメントアウトし、後述のイメージ名のオプションの*_image_repositoryを初期値のままにしておけば、デプロイだけ処理します。
このplaybookでは動作環境としてK3sをインストールしてそこへAWXをデプロイします。K3sで標準で使用できるIngressを使って外部公開するため、アクセス用FQDNawx_service_fqdnに指定します。ここで指定したFQDNDNShostsファイルを使って名前解決できるようにすればwebアクセスできます。

項目 説明
build ホストグループ イメージビルドするホストを指定
deployホストグループ AWXをデプロイするホストを指定 (buildと同じでOK)
awx_service_fqdn Ingressで使用するアクセス用FQDN
awx_namespace AWXをデプロイするk8sのネームスペース
registry_username ビルドしたイメージをpushするレジストリのログインユーザー名
registry_password ビルドしたイメージをpushするレジストリのログインパスワード
registry_url ビルドしたイメージをpushするレジストリURL (省略時はDocker Hub)

イメージのレジストリURLに関するオプションは以下。
未指定の場合は前述の通りregistry_urlregistry_usernameを元にレジストリURLが決まりますが、これらの変数でレジストリURLを上書きできます。
自分のアカウントのレジストリへpushする場合は、exampleのインベントリファイルの設定はコメントアウトしてください。

項目 説明
operator_image_repository ビルド・デプロイするawx-operatorのイメージ名
awx_image_repository ビルド・デプロイするawxのイメージ名
awx_ee_image_repository ビルド・デプロイするawx-eeのイメージ名

リソース制限のオプションは以下。
未指定の場合はデフォルト値が使用されます。デプロイ先のノードスペックが低い場合等はlimitsの値をノードサイズに収まるように指定してください。
(例えば2コア・RAM12GBの場合は、CPU 400m・メモリ1GBとかにすれば大体行けます)

項目 説明
web_resource_requests_cpu Web containerのCPU requests値
web_resource_requests_mem Web containerのメモリrequests値
web_resource_limits_cpu Web containerのCPU limits値
web_resource_limits_mem Web containerのメモリlimits値
task_resource_requests_cpu Task containerのCPU requests値
task_resource_requests_mem Task containerのメモリrequests値
task_resource_limits_cpu Task containerのCPU limits値
task_resource_limits_mem Task containerのメモリlimits値
ee_resource_requests_cpu EE control plane containerのCPU requests値
ee_resource_requests_mem EE control plane containerのメモリrequests値
ee_resource_limits_cpu EE control plane containerのCPU limits値
ee_resource_limits_mem EE control plane containerのメモリlimits値

バージョンに関するオプションは以下。
該当Gitリポジトリのタグorブランチ名に対応しています。
ただし、現状では指定のバージョン以外の動作は基本的に未確認。

項目 説明
operator_version AWX Operatorのバージョン
awx_version AWXのバージョン
awx_ee_version AWX EEのバージョン
ansible_runner_version Ansible Runnerのバージョン
ansible_builder_version Ansible Builderのバージョン
python_base_version python-base-imageのバージョン
python_builder_version python-builder-imageのバージョン
receptor_version Receptorのバージョン

実行

インベントリファイルが用意できたら、ansible-playbookコマンドで実行。

$ ansible-playbook -i inventory.ini playbook.yml

[build]ホストグループに指定したホストに対してDockerのインストールを行い、必要な全イメージをbuildし、K3sへデプロイするイメージをpushします。
push対象のレジストリには、インベントリに指定したユーザー名とパスワードで認証を行います。

ビルドが完了したら、[deploy]ホストグループに指定したホストへK3sをインストールし、ビルド処理でpushしたコンテナイメージをデプロイします。
デプロイ後しばらく待てば、awx_service_fqdnに指定したFQDNにブラウザでアクセスすればAWXの画面が表示されます。
(クラウドのセキュリティグループは別途80/TCPを許可しておいてください)

AWXへログインするには、ユーザー名はadminで、パスワードは以下のコマンドで確認できます。

$ kubectl get secret -n awx awx-demo-admin-password -o jsonpath="{.data.password}" | base64 -d

解説 (という名の備忘録)

AWX 20.0.0はKubernetesクラスタへデプロイするために、operatorのイメージ(awx-operator)と、AWX本体(awx)Ansible Execution Environment for AWX(awx-ee)の3つのイメージが必要です。
awx-operatorawxはarm64環境でそれぞれビルドすればよいので簡単ですが、awx-eeのイメージは、ベースイメージや内部で使用する実行用バイナリファイルもamd64版しかないため、以下のイメージのビルドも必要です。

awx-eeのイメージビルドにはansible-runneransible-builderが必要で、ansible-runneransible-builderはどちらもpython-baseansible-builderが必要です。この4イメージはmainブランチや最新リリース版をビルドすれば現状は大丈夫です。イメージビルドはpython-base/python-builderイメージをビルドしたあとにansible-runner/ansible-builderをビルドします。 また、/usr/bin/receptorとしてreceptorイメージから実行ファイルをコピーしているので、receptorイメージもarm64環境でビルドする必要があります。

python-base

github.com

ビルド手順のドキュメントは特に見当たらなかったけど、リポジトリルートにContainerfileがあるのでこのファイルでdocker buildします。
後続のansible-runneransible-builderのビルドを簡単にするために、quay.io/ansible/python-base:latestというタグを指定しておきます。

python-builder

github.com

これもpython-baseと同様に、Containerfileがあるのでこのファイルでdocker buildします。
また、これもansible-runneransible-builderのビルド用にquay.io/ansible/python-builder:latestタグを指定します。

ansible-runner

github.com

これはMakefileがあるので、makeを使ってビルドします。ファイルをチェックするとimageというターゲットがあるので、以下のコマンドでイメージビルドできます。
ビルドにはquay.io/ansible/python-base:latestquay.io/ansible/python-builder:latestが必要なので、必ず事前にarm64版をローカルに保持しておきます。(無い場合quayからamd64版をpullしてしまうので実行時にエラーになる)

$ make image

ansible-builder

github.com

これもansible-runnerと同様にMakefileがあるのでmakeコマンドを使います。同じく'image`ターゲットがあるので、コマンドは以下です。
ansible-runner同様に、quay.io/ansible/python-base:latestquay.io/ansible/python-builder:latestが必要なので事前にarm64版をビルドしておきます。

$ make image

receptor

github.com

receptorは他よりちょっとハマりがあって、2022.03時点の最新リリース版のv1.1.1だと、ベースイメージにcentos:8が指定されており、CentOS 8はすでにサポート終了してYumリポジトリが無効になっているため、以下のエラーメッセージでイメージビルドに失敗します。

Error: Failed to download metadata for repo 'appstream': Cannot prepare internal mirrorlist: No URLs in mirrorlist

ということで1.1系はこの状態っぽいので、1.0系を使うとよさげ。
現在1.2の開発が行われてるっぽいですが、最初に試した2月時点だと、golangのインストールをパッケージでなく RUN curl -LO https://go.dev/dl/go1.16.13.linux-amd64.tar.gz を実行して行っており、当時はダウンロードするバイナリのURLをarm64版に修正するなど試したりして回避してたので、開発版や1.2は様子見で、少し古いリリース版を使うのが現状では手間が少なそうです。

awx-ee

github.com

依存する5つのarm64版イメージビルドが完了したらawx-eeをビルドすればOK。これらの依存イメージはpushする必要はなく、ローカルにあればそれをベースイメージとしてビルドします。

awx-eeのビルドはREADMEに書かれており、toxコマンドを使ってDockerの場合は以下を実行します。

$ tox -edocker

ただawx-eeのイメージは原因が結局よくわからなかったんだけど、これでビルドしたイメージでデプロイしたAWXで、(Demoを含む)ジョブテンプレート実行時に以下のPermissionErrorが発生しました。

Traceback (most recent call last):
  File "/var/lib/awx/venv/awx/lib64/python3.9/site-packages/awx/main/tasks/jobs.py", line 449, in run
    self.pre_run_hook(self.instance, private_data_dir)
  File "/var/lib/awx/venv/awx/lib64/python3.9/site-packages/awx/main/tasks/jobs.py", line 979, in pre_run_hook
    RunProjectUpdate.make_local_copy(job.project, private_data_dir, scm_revision=job_revision)
  File "/var/lib/awx/venv/awx/lib64/python3.9/site-packages/awx/main/tasks/jobs.py", line 1380, in make_local_copy
    source_branch = git_repo.create_head(tmp_branch_name, p.scm_revision)
  File "/var/lib/awx/venv/awx/lib64/python3.9/site-packages/git/repo/base.py", line 386, in create_head
    return Head.create(self, path, commit, force, logmsg)
  File "/var/lib/awx/venv/awx/lib64/python3.9/site-packages/git/refs/symbolic.py", line 543, in create
    return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg)
  File "/var/lib/awx/venv/awx/lib64/python3.9/site-packages/git/refs/symbolic.py", line 510, in _create
    ref.set_reference(target, logmsg)
  File "/var/lib/awx/venv/awx/lib64/python3.9/site-packages/git/refs/symbolic.py", line 326, in set_reference
    assure_directory_exists(fpath, is_file=True)
  File "/var/lib/awx/venv/awx/lib64/python3.9/site-packages/git/util.py", line 177, in assure_directory_exists
    os.makedirs(path, exist_ok=True)
  File "/usr/lib64/python3.9/os.py", line 225, in makedirs
    mkdir(name, mode)
PermissionError: [Errno 13] Permission denied: '/var/lib/awx/projects/_6__demo_project/.git/refs/heads/awx_internal'

検索するとIssueでも見つかったけど、結局どう解決したかわからず…

いろいろ試行錯誤(↑のツリー参照)した結果、k8sへデプロイした際にawx-eeの実行ユーザーがrootになってるのがマズイっぽい。
AWXのpodは、awxとawx-eeの複数コンテナから構成されており、awxコンテナはuid=1000(awx)で動作しているのに対して、awx-eeコンテナはuid=0(root)で動作した状態で、両方のコンテナからemptyDirで/var/lib/awx/projectsを共有している、というpod構成。
ジョブテンプレート実行時には、awx-eeコンテナでリポジトリをcloneするため、.git以下にroot権限でGitの制御ファイルが作成され、このファイルをawxコンテナからの参照(更新?)時に権限エラーになっている、というのが原因のようでした。

この問題を回避するため、コンテナの実行権限をコントロールできたら早そうだけどOperatorのカスタムリソース経由のためだいぶ厳しそうだったので、コンテナビルドの段階で実行ユーザーの指定を入れました。
具体的には、一旦awx-eeイメージを普通にビルドし、そのawx-eeイメージをベースイメージとして、更にUSER 1000だけ指定したDockerfileでビルドするというもの。この追加のイメージであれば、実行時にawxコンテナと同じuid=1000で動作するため、ジョブテンプレートも正常に動作しました。

awx

github.com

これはMakefileはあるけど、使い方がよくわからなかった。docker-composeでイメージビルドできるっぽいけど多分Docker Compose環境用らしく、K8sへデプロイしてもストレージドライバ周りでエラーが出たので使えなかった。
(ブログ作成時に改めてよくみたらawx-kube-dev-buildというターゲットがK8s用イメージビルドかも…未確認)

リポジトリ内のファイルをチェックすると、Dockerfileを生成してるplaybookがtools/ansibleディレクトリ以下にあるので、このplaybookを使ってイメージビルドを確認できた。実行するコマンドは以下の通り。

$ ansible-playbook build.yml

インベントリファイルなどで以下の変数を定義して実行時に指定すれば、このplay実行によってレジストリへのpushも実行される。

[all:vars]
push=1
registry=docker.io
awx_image=zakihmkc/awx
awx_image_tag=20.0.0

ただ今回作った自作のplaybookでは、レジストリとイメージパスを分割で定義されると都合が悪かったのでこの機能は使わず、自前でpushしてます

awx-operator

github.com

Makefileがあるのでmakeコマンドでビルドできます。
ansibleディレクトリplaybooksディレクトリはあるんだけど、ビルド用のplaybookファイルは見当たらなかったので、多分makeするしかないかも。以前デプロイのつもりでミスってビルドしてしまったこともあるので…(笑)

ターゲットはdocker-buildで、IMGパラメタを指定すれば、ビルドするイメージのタグも指定できるので、自分のレジストリへpushするようにタグ指定しておくと便利です。

$ make docker-build IMG=zakihmkc/awx-operator:0.17.0

試してないけどmake docker-pushでpushもできると思う。


参考情報

2月中旬にAWX 19.5.1のときに試した作業ログ。

zaki-lknr.github.io

CentOS 8のリポジトリがクローズした直後だったらしく、タイミングが良かったのやら悪かったのやら…(笑)

あと、去年の時点で同じようにarm64環境(Raspberry Pi)向けにビルド・デプロイされてる方もいらっしゃいました。
receptorのバイナリ取得がシンプルで良いですね。。

qiita.com

あと今回はコンテナ操作が多かったですが、発売されたばかりのこちらの本にDockerやKubernetesで使えるサンプルコードがたくさん載ってるので、使い方をさっと確認できて便利でした(ステマ)

book.impress.co.jp