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

[Ansible] metaモジュールを使ってPlayの処理を途中で終了する

プログラミングだとreturnexit(0)などで、以降の処理が不要な場合に、その時点で処理を終了させることができる。
Ansibleのplaybookでも似たようなことができ、metaモジュールを使用し、任意のタイミングで処理を終了させるタスクを作成できる。

docs.ansible.com

このモジュールはパラメタに指定するワードによっていろいろな動作を指せることができ、本エントリではend_playend_hostについて説明する。

以下、ansible-core 2.12.2で確認。

end_play

Playの実行をその時点で終了する。

---
- hosts: localhost
  gather_facts: false

  tasks:
  - name: task1
    debug:
      msg: task1

  - name: task2
    debug:
      msg: task2

  - name: end play
    ansible.builtin.meta: end_play

  - name: task3
    debug:
      msg: task3

このplaybookの内容でAnsibleを実行すると、end playタスクで処理が終了し、task3は実行されない。

PLAY [localhost] ****************************************************************

TASK [task1] ********************************************************************
ok: [localhost] => 
  msg: task1

TASK [task2] ********************************************************************
ok: [localhost] => 
  msg: task2

TASK [end play] *****************************************************************

PLAY RECAP **********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

条件によって以降の全ての処理を丸ごとスキップしたい場合に使える。

end_host

end_playが全処理を終了するのに対し、end_hostは指定ホストを対象とした処理のみその時点で終了する。
たとえば処理対象ホストにRHELホストとDebianホストがあり、Debianの場合は後続の処理が何もないので処理終了させて、RHELは続きの処理を行う、みたいなことができる。

---
- hosts: all
  gather_facts: true
  gather_subset:
    - min

  tasks:
  - name: task1
    debug:
      msg: task1

  - name: end host
    ansible.builtin.meta: end_host
    when: ansible_os_family == 'Debian'

  - name: task2
    debug:
      msg: task2

  - name: task3
    debug:
      msg: task3

このplaybookの内容でAnsibleを実行すると、facts変数のansible_os_familyの値がDebianのホストはend hostタスク以降の処理が行われず、それ以外のホストはtask2task3と処理が継続する。

実行例の処理対象ホストは以下の通り。

host distro
cheddar Debian
cloud-dev CentOS

これでansible-playbookを実行すると以下の通り。

PLAY [all] **********************************************************************

TASK [Gathering Facts] **********************************************************
ok: [cheddar]
ok: [cloud-dev]

TASK [task1] ********************************************************************
ok: [cloud-dev] => 
  msg: task1
ok: [cheddar] => 
  msg: task1

TASK [end host] *****************************************************************
skipping: [cloud-dev]

TASK [end host] *****************************************************************

TASK [task2] ********************************************************************
ok: [cloud-dev] => 
  msg: task2

TASK [task3] ********************************************************************
ok: [cloud-dev] => 
  msg: task3

PLAY RECAP **********************************************************************
cheddar                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
cloud-dev                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

(追記) 複数のPlayの場合

end_playend_hostでPlayが終了しても、後続のPlayがある場合は、そこからは処理が始まる。

---
- name: play1
  hosts: localhost
  gather_facts: false

  tasks:
  - name: task1
    debug:
      msg: task1

  - name: task2
    debug:
      msg: task2

  - name: end play
    ansible.builtin.meta: end_play

  - name: task3
    debug:
      msg: task3

- name: play2
  hosts: localhost
  gather_facts: false

  tasks:
  - name: task1
    debug:
      msg: task1

このようにplay1のPlayがend_playで終了した場合、task3は処理されないがplay2は処理が始まる。
実行結果は以下。

PLAY [play1] ********************************************************************

TASK [task1] ********************************************************************
ok: [localhost] => 
  msg: task1

TASK [task2] ********************************************************************
ok: [localhost] => 
  msg: task2

TASK [end play] *****************************************************************

PLAY [play2] ********************************************************************

TASK [task1] ********************************************************************
ok: [localhost] => 
  msg: task1

PLAY RECAP **********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

end_hostも同様。

---
- name: play1
  hosts: all
  gather_facts: true
  gather_subset:
    - min

  tasks:
  - name: task1
    debug:
      msg: task1

  - name: end host
    ansible.builtin.meta: end_host
    when: ansible_os_family == 'Debian'

  - name: task2
    debug:
      msg: task2

  - name: task3
    debug:
      msg: task3

- name: play2
  hosts: all
  gather_facts: false

  tasks:
  - name: task1
    debug:
      msg: task1

この内容のPlaybookで実行すると、play2は全ホストで処理が開始する。

PLAY [play1] ********************************************************************

TASK [Gathering Facts] **********************************************************
ok: [cheddar]
ok: [cloud-dev]

TASK [task1] ********************************************************************
ok: [cloud-dev] => 
  msg: task1
ok: [cheddar] => 
  msg: task1

TASK [end host] *****************************************************************
skipping: [cloud-dev]

TASK [end host] *****************************************************************

TASK [task2] ********************************************************************
ok: [cloud-dev] => 
  msg: task2

TASK [task3] ********************************************************************
ok: [cloud-dev] => 
  msg: task3

PLAY [play2] ********************************************************************

TASK [task1] ********************************************************************
ok: [cloud-dev] => 
  msg: task1
ok: [cheddar] => 
  msg: task1

PLAY RECAP **********************************************************************
cheddar                    : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
cloud-dev                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

後続のPlayは実行されるので、イメージとしてはexit()ではなくreturnだね。


whenを使ってスキップしてももちろん良いが、実行しない後続のタスク定義数が多い場合はend_hsotでスパッと抜けるという実装もできる。
え、MISRA-C準拠で実装してる?playbookを?まさかそんな。。

[Linux] ProxyJump設定でSSHの多段アクセスとscp/ポートフォワード

検証環境へアクセスするために踏み台サーバ―を経由しないとアクセスできないとか、踏み台サーバーを3つ経由しないと本番環境へアクセスできないとか、そんな場合でもsshで1コマンドでアクセスするためのオプション指定について、man sshを眺めていたらたまたまProxyJumpというオプションが目に留まったので確認してみた。

環境

ローカルホストから、

  1. ssh 192.168.0.16 (踏み台1)
  2. ssh 172.16.1.0 (踏み台2)
  3. ssh 172.29.0.89 (お目当てのターゲットホスト)

という順序でSSHアクセスしたい場合、以前は(というより今でも多くのところで)ProxyCommandを使って多段アクセスしていたが、(2016年リリースのOpenSSH 7.3で)ProxyJumpオプションが追加されており、このオプションを使うとより簡潔なオプション指定で多段アクセスを実現できる。

図にするとこんな構成。

ProxyJumpを使った多段アクセス

[zaki@cloud-dev ~]$ ssh 172.29.0.89 -J 192.168.0.16,172.16.1.0
zaki@192.168.0.16's password: 
zaki@172.16.1.0's password: 
zaki@172.29.0.89's password: 
Last login: Wed Feb  2 21:10:16 2022 from 172.29.0.10
[zaki@restricted-node ~]$
[zaki@restricted-node ~]$ ip a s ens192
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0c:29:77:f6:c4 brd ff:ff:ff:ff:ff:ff
    inet 172.29.0.89/24 brd 172.29.0.255 scope global noprefixroute ens192
       valid_lft forever preferred_lft forever

この通り、-Jオプションにアクセスしたい順番にカンマ区切りで踏み台ホストを羅列すれば、記述した順序でSSHアクセスして最終的にお目当てのターゲットホストへログインする動作になる。

ユーザー名やポートが異なるサーバーがある場合

踏み台の特定ホストだけユーザー名やポート番号を指定する場合は、username@host:portの書式で記述する。
途中の踏み台の172.16.1.025022でListenしていて、さらにsample-userユーザーでアクセスする場合は以下の通り。

[zaki@cloud-dev ~]$ ssh 172.29.0.89 -J 192.168.0.16,sample-user@172.16.1.0:25022
zaki@192.168.0.16's password: 
sample-user@172.16.1.0's password: 
zaki@172.29.0.89's password: 
Last login: Wed Feb  2 21:10:49 2022 from 172.29.0.10
[zaki@restricted-node ~]$ 

鍵認証

ここまでの例はコマンド実行例からも分かる通り、パスワード認証でログインしている。
公開鍵認証については、ドキュメントからはちょっと読み取れなかったけど、コマンドラインオプションで指定できる秘密鍵は、お目当てのターゲットホストに登録してある公開鍵のみに作用している。
踏み台サーバーの鍵認証用の秘密鍵コマンドラインでは指定できなさそう。

ターゲットホストとの公開鍵認証はオプション指定で従来通り特に問題なく実行できる。
ローカルにある秘密鍵~/.ssh/id_rsa_4096の公開鍵が、ターゲットホストの~/.ssh/authorized_keysに登録済みであれば、以下の通り。

[zaki@cloud-dev ~]$ ssh 172.29.0.89 -J 192.168.0.16,sample-user@172.16.1.0:25022 -i ~/.ssh/id_rsa_4096
zaki@192.168.0.16's password: 
sample-user@172.16.1.0's password: 
Last login: Wed Feb  2 21:18:53 2022 from 172.29.0.10
[zaki@restricted-node ~]$ 

172.29.0.89アクセス時のパスワード認証が行われず、鍵認証でログインできている。
踏み台サーバーも鍵認証を行いたい場合は、少なくともssh_configに記述すれば可能。

ssh_config基本

まずは鍵認証なしの場合の記述。
踏み台2つ目以降は、その前段の踏み台のホストが何かをproxyjumpの行に記述する。

Host proxy1
    hostname 192.168.0.16
Host proxy2
    hostname 172.16.1.0
    user sample-user
    port 25022
    proxyjump proxy1
Host target
    hostname 172.29.0.89
    proxyjump proxy2

踏み台サーバーへの秘密鍵の指定がある場合は、それぞれ指定すればOK

Host proxy1
    hostname 192.168.0.16
    identityfile ~/.ssh/id_rsa_4096
Host proxy2
    hostname 172.16.1.0
    user sample-user
    port 25022
    identityfile ~/.ssh/id_rsa_nopass
    proxyjump proxy1
Host target
    hostname 172.29.0.89
    identityfile ~/.ssh/id_rsa_4096
    proxyjump proxy2

それぞれ対応した公開鍵をそれぞれのホストに登録してあれば、ターゲットホストへのSSHアクセスのコマンドで一気にログインできる。
(パスフレーズが設定されてればもちろん入力は必要だけど)

[zaki@cloud-dev ~]$ ssh target 
Last login: Wed Feb  2 21:36:41 2022 from 172.29.0.10
[zaki@restricted-node ~]$ 

ポートフォワード

ターゲットホスト上からlocalhost:8888でアクセスできるwebサーバーがあるとして、このwebサーバーにSSHを実行するローカルからアクセスしたい場合について。といっても、通常のローカルポートフォワードと同じく-Lを使えばOK。
ローカルホストの8889/TCPをターゲットのlocalhost:8888にフォワードするには、-L 8889:localhost:8888を指定する。

[zaki@cloud-dev ~]$ ssh 172.29.0.89 -J 192.168.0.16,sample-user@172.16.1.0 -L 8889:localhost:8888
zaki@192.168.0.16's password: 
sample-user@172.16.1.0's password: 
zaki@172.29.0.89's password: 
Last login: Wed Feb  2 23:08:47 2022 from 172.29.0.10
[zaki@restricted-node ~]$

この状態で確認用webサーバーを起動。

[zaki@restricted-node ~]$ ls -aF
./  ../  .bash_history  .bash_logout  .bash_profile  .bashrc  .ssh/
[zaki@restricted-node ~]$ python -m SimpleHTTPServer 8888
Serving HTTP on 0.0.0.0 port 8888 ...

これで、SSH接続元のブラウザでlocalhost:8889にアクセスすると、ターゲットホストのlocalhost:8888フォワードされる。

ターゲットホストに対するローカルポートフォワード込みのssh_configは以下の通り。
(踏み台の記述は変更無し)

Host target
    hostname 172.29.0.89
    identityfile ~/.ssh/id_rsa_4096
    proxyjump proxy2
    localforward 8889 localhost:8888

※ SimpleHTTPServerについては以下参照。

qiita.com

scpの多段転送

[zaki@cloud-dev ~]$ scp ./kubeconfig.yaml target:/var/tmp

ssh_configが設定されていれば、これで一気にターゲットホストへ転送できる。
ホップ数によってはこれがかなり便利。

ssh_config設定無しでコマンドラインオプションで指定するには、scpには-Jオプションがないため、-oProxyJumpを指定する。(※ 後述)

[zaki@cloud-dev ~]$ scp -o 'ProxyJump 192.168.0.16,sample-user@172.16.1.0' ./kubeconfig.yaml 172.29.0.89:/var/tmp
zaki@192.168.0.16's password: 
sample-user@172.16.1.0's password: 
zaki@172.29.0.89's password: 

2023.08.01追記:
scpにもOpenSSH 8.0で-Jオプションに対応していた模様。ssh-Jと同様に踏み台の指定を行う。
ただし現時点(OpenSSH 8.8)では、-Jオプションを転送元・転送先ファイル名の後に指定すると踏み台の指定がファイル名と認識されてしまうため、コマンドの直後に指定する。

### 後ろにオプション指定した場合
$ scp ./memo.md  172.16.1.3:~ -J 192.168.0.16
192.168.0.16: No such file or directory

### 先にオプション指定した場合
$ scp -J 192.168.0.16 ./memo.md  172.16.1.3:~
zaki@172.16.1.3's password: 

WindowsSSH

動作する。

PS C:\Users\zaki> ssh -V
OpenSSH_7.7p1, OpenSSL 1.0.2o  27 Mar 2018
PS C:\Users\zaki> ssh 172.29.0.89 -J 192.168.0.19,172.16.1.0
zaki@192.168.0.19's password:
zaki@172.29.0.89's password:
Last login: Sat Jan 29 20:13:16 2022 from 172.29.0.10
[zaki@restricted-node ~]$

5年以上前に機能追加されてたなんて知らなかった。。
ついでにいうとssh-copy-idコマンドもしばらく知らなかった←

[Ansible Builder]パッケージインストールに追加のリポジトリ設定やファイルコピーが必要な場合のイメージビルド

本記事はAnsible Advent Calendar 2021の14日目のエントリです。
珍しくインプットが湧いてきた1ため2日連続です笑

adventar.org

環境その他もろもろは、以前Ansible Builderについて試したこの記事のときのものと同じです。

解決したい課題

7月に一度、新しいAnsibleの実行環境であるExecution Environment(以下EE)をお試し実行してみましたが、この時「OSパッケージインストール周りでリポジトリ追加したい場合なんかは現状不明。」という課題を残したまま放置してました。

zaki-hmkc.hatenablog.com

当時は「イメージ内に追加で必要なパッケージはbindep.txtに記述する」機能から発想が縛られて、「一度ビルドを実行すると生成されるcontext/Containerfileファイルがあるため、内容を書き換えて(as builderの後にリポジトリを追加する処理を差し込む)リビルド」というかなり無理やりなことをしていました。

というのも、EEの定義ファイル(デフォルトexecution-environment.yml)にパッケージインストール関連で記載できるのはbindep.txtにパッケージ名、あとリポジトリの変更を行いたければadditional_build_steps以下に記載はできるけど、肝心の処理順序が「bindepによるパッケージインストール」が先で、「additional_build_stepsprependの処理」が後(※)のため、bindep.txtリポジトリ追加が必要なパッケージを記載しても、そのリポジトリ追加を行うタイミングが無い、というモノでした。(鶏卵 ちょっと違うか…)

※12/14夜追記: 正確には、イメージビルドの内訳として、builderのイメージビルド(FROM $EE_BUILDER_IMAGE as builderの部分)と、runnerのイメージビルド(FROM $EE_BASE_IMAGEの部分)があり、最後のrunnerのイメージビルドはadditional_build_stepsに記述した処理のあとにbindep.txtを参照したパッケージインストールが行われるので期待通りの動きをするが、中間にあるbuilderのイメージビルドはadditional_build_stepsの処理は行わずにbindep.txtを使ったパッケージインストールを行う処理内容になっている。詳細はビルド時に生成されるContainerfileを参照。

回避策

実際のところ、Execution Environment Definitionを見ても、リポジトリ設定の余地は現状ないので、機能的には無さそう。

ansible-builder.readthedocs.io

ただ、そもそも7月の時点でどうして思いつかなかったんだ、というレベルだったりするけど、additional_build_stepsの中で、

  1. リポジトリを追加する処理
  2. 追加したリポジトリからパッケージインストールする処理

をどちらも書いてしまえばいいじゃん、というのを思いついたのでお試し。

お題は前回と同じくKubernetesCLIコマンドのインストール。

execution-environment.yml (抜粋)

additional_build_stepsの部分は以下の通り。

additional_build_steps:
  prepend:
    - COPY kubernetes.repo /etc/yum.repos.d/kubernetes.repo
    - RUN dnf install -y kubectl

Kubernetesのドキュメントではcatとヒアドキュメントでファイル生成してるけど、ちょっとコツがいるので素直に外部ファイル化してCOPY使っている。
(これはこれでコツがいるんだけど…後述)

なお、EEのベースイメージはCentOSベースなので、パッケージもCentOSの場合の手順を実施。

kubernetes.repo (追加ファイル)

これはドキュメントの通りの内容。
インライン記載は後述してるけど、別途ファイル用意した方が(今回の場合は)ミスは少ないと思う。

[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg

ビルド

execution-environment.ymlなどの制御に使用するファイル以外の、イメージ内部に追加したい(今回のkubernetes.repoファイルのような)追加ファイルがある場合は、ビルド時にコンテキストの指定が必要。

$ ansible-builder build --help

  [...]

  -c BUILD_CONTEXT, --context BUILD_CONTEXT
                        The directory to use for the build context (default: context)

これはデフォルドではcontextというディレクトリが指定されるが、このディレクトリがビルド時のルートディレクトリとして扱われるイメージ。もっといえば、ここがchrootされる感じ。
つまり、イメージへ追加したいファイルがある場合は、コンテキストで指定したディレクトリ以下になければ、ビルド時にファイルを(例えフルパスで指定してたとしても、ビルド時のルートはコンテキストで指定したディレクトリになるため)認識できない。

よって、具体的なコマンドラインは、execution-environment.ymlkubernetes.repoのあるディレクトリで以下の通り。

$ ls -F
Containerfile  bindep.txt                 kubernetes.repo   requirements.yml
ansible.cfg    execution-environment.yml  requirements.txt
$ ansible-builder build --tag kube-sample:devel --context .

これで、イメージビルド時のCOPYの対象であるkubernetes.repoを認識してイメージへコピーできるようになる。

ファイル数が多く、execution-environment.ymlなどとは分離したい場合は、「イメージへ組み込みたいファイル用のディレクトリを作成し、--contextでそのディレクトリを指定」すればOK.
その場合でも、COPYの引数に指定するファイルのパスは、--contextで指定するディレクトリ基準の相対パスにすれば良い。

確認

$ ansible-builder build --tag kube-sample:devel --context files
Running command:
  podman build -f files/Containerfile -t kube-sample:devel files
Complete! The build context can be found at: /home/zaki/ee/kube/files
$ podman run -it --rm localhost/kube-sample:devel bash
bash-4.4# cat /etc/yum.repos.d/kubernetes.repo 
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
bash-4.4# kubectl version --short --client
Client Version: v1.23.0

ちゃんとできている。

ヒアドキュメントは…無理かな?

これはダメだった例。

  prepend: |
    COPY <<EOF /etc/yum.repos.d/kubernetes.repo
    [kubernetes]
    name=Kubernetes
    baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
    enabled=1
    gpgcheck=1
    repo_gpgcheck=1
    gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
    EOF

これはワンチャン行けるかなと思ったけど、構文エラーにはならずビルドは動いて、COPYのタイミングで「<<EOFというファイルが無い」というエラーとなった。

STEP 17: COPY <<EOF /etc/yum.repos.d/kubernetes.repo
Error: error building at STEP "COPY <<EOF /etc/yum.repos.d/kubernetes.repo": checking on sources under "/home/zaki/ee/kube/context": copier: stat: "/<<EOF": no such file or directory

インラインでやるならこんな感じ。
echo-eオプションを付加し、\nで改行しつつ、行末の\で次行へ続ける記述でファイル作成を確認。

  prepend: |
    RUN echo -e "[kubernetes]\n\
    name=Kubernetes\n\
    baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64\n\
    enabled=1\n\
    gpgcheck=1\n\
    repo_gpgcheck=1\n\
    gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg" > /etc/yum.repos.d/kubernetes.repo
    RUN dnf install -y kubectl

確認した環境

$ cat /etc/redhat-release 
Fedora release 34 (Thirty Four)
$ ansible-builder --version
1.0.1
$ python --version
Python 3.9.4

おまけというか備忘録
additional_build_steps以下prependへのコマンドの指定は、リストでも複数行テキストでもどっちでもOKでした。


  1. 同じシチュエーションの対応策に困ってる部署のメンバがいて、回避策を偶然思いついた、というものw やはり業務を通して(にゃーん

[Ansible] fetchモジュールを使ってリモートのファイルを実行ノードへ転送・集約する

最近アウトプットできてなかったけど、これはAnsible Advent Calendar 2021の13日目のエントリです。

adventar.org

個人的にあまりなじみがないfetchモジュールについての備忘録です。
このモジュールはcopyモジュールの逆で、マネージドノード上のファイルをコントロールノードへコピーします。システム構築の自動化でAnsaibleを使うようなシチュエーションだと割と使用頻度が低い(個人の感想)のですが、サービスの日々の運用で、各ホスト上の例えばログファイルを1か所へ集約したい場合に本領を発揮します(当社比)。

docs.ansible.com

基本の使い方

  tasks:
    - name: fetch file
      ansible.builtin.fetch:
        src: /var/log/messages
        dest: /var/tmp/fetch-test

モジュールの基本機能は、リモートホストsrcに指定したファイルを、Ansible実行ホストのdestで指定したディレクトリ以下にリモートホスト名/リモート上のフルパスというパスでコピーする。
このとき、destディレクトリが無くても自動で作成される。

flatパラメタ

コピー時のリモートのパスやリモートホスト名が不要で、destに指定したパスに直接集めたい場合は、flatパラメタを指定する。

    - name: fetch file
      ansible.builtin.fetch:
        src: /var/log/messages
        dest: /var/tmp/fetch-test
        flat: true

flatパラメタをtrueにすると、destに指定したパスが転送先になる。
この例だと、リモートの/var/log/messagesファイルは/var/tmp/fetch-testというファイル名で転送される。

また、

    - name: fetch file
      ansible.builtin.fetch:
        src: /var/log/messages
        dest: /var/tmp/fetch-test/  # '/' 追加
        flat: true

このようにdestの指定をディレクトリ(末尾/)にすると、ファイル名は維持され転送先は/var/tmp/fetch-test/messagesになる。

ただしflatを使う場合、処理対象のホストが複数ありファイル名が同一だとすべて同じファイルで転送されるため、処理順が最後のホスト以外のファイルは上書きされてしまうので注意。
flatを使用しつつ、ファイル名にホスト名を付与したいのであれば、マジック変数のinventory_hostnameを使うなどしてパスやファイル名がホスト毎になるように調整する。

        dest: "/var/tmp/fetch-test/{{ inventory_hostname }}/"

docs.ansible.com

fail_on_missingパラメタ

デフォルトの動作は、リモートホスト上にsrcで指定したファイルが無い場合は失敗となる。
要件によって「ファイルが無い場合は何もしない(正常完了)」したい場合は、fail_on_missingパラメタにfalseを指定する。
(ファイルが存在しない場合以外の転送失敗なども軒並み無視するignore_errorsなどを使わないよーに)

    - name: fetch file
      ansible.builtin.fetch:
        src: /var/log/messages
        dest: /var/tmp/fetch-test
        fail_on_missing: false

このオプションは、古いAnsibleバージョンだとデフォルト値が逆になっているので(バージョン塩漬けのような)環境によっては動作が異なるので注意。

ディレクトリ以下を再帰的にコピーするには…

fetchモジュールには再帰コピーの機能はない1ため、自力で処理する必要がある。
ファイルリストを作成するにはfindモジュール辺りを使ったタスクを別途用意する。

    - name: create file list
      ansible.builtin.find:
        paths: /var/log
        recurse: true
      register: register_file_stat

    - name: fetch files
      ansible.builtin.fetch:
        src: "{{ item }}"
        dest: /var/tmp/fetch-test
      loop: "{{ register_file_stat.files | map(attribute='path') }}"

ただしこのとき、ファイル数やサイズが大量で転送に時間がかかる場合でかつ、リモートホスト上のファイルの作成や削除が頻繁に発生するような環境の場合、リスト作成時点から転送までのタイムラグによってファイルの有無が変化すると転送失敗になったりするので注意が必要。

対象ディレクトリをarchiveモジュールでファイルに固めて転送する方が良い場合もあるので、要件によって検討する。
手順は増えるが、転送後のアーカイブファイルの展開と削除も必要に応じて行う。

    - name: archive files
      community.general.archive:
        path: /var/log
        dest: /tmp/log.tar.gz

    - name: fetch file
      ansible.builtin.fetch:
        src: /tmp/log.tar.gz
        dest: /var/tmp/fetch-test

ちなみにarchiveモジュールは普段使わないから気付かなかったけど、アーカイブ対象に変化が無かったら実行ステータスはokになって冪等性が担保されるように見えるけど、その場合でもアーカイブファイルのタイムスタンプが更新されてしまうため、その後のタスクの冪等性を担保するのはすこし難しいかも。
(ファイルの更新は最新のcommunity.general 4.1.0でも再現)

ただ、「定期実行でそのタイミングのスナップショット的にファイル構造を丸ごと保存したい」みたいな要件の場合は、アーカイブ式の方が扱いやすいかも。

環境

$ ansible --version
ansible [core 2.11.3] 
  config file = /home/zaki/.ansible.cfg
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/venv/ansible4.2.0/lib64/python3.9/site-packages/ansible
  ansible collection location = /home/zaki/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/zaki/venv/ansible4.2.0/bin/ansible
  python version = 3.9.2 (default, Mar  5 2021, 01:49:45) [GCC 8.4.1 20200928 (Red Hat 8.4.1-1)]
  jinja version = 3.0.1
  libyaml = True


  1. 現状のモジュールのドキュメントに「Recursive fetching may be supported in a later release.」とそのうち実装されるかも的な記述はあるけど。

[Oracle Linux] arm64アーキテクチャのA1.FlexのVMでDocker版NetBoxをビルド&デプロイする (Ubuntu / Oracle Linux)

Oracle Cloudで無料で使えるA1.FlexシェイプのVM(arm64アーキテクチャ)でNetBoxをDockerでデプロイしてみました。
NetBoxのコンテナイメージはamd64版しか公開されていないため、arm64アーキテクチャでコンテナ版をデプロイするには、自前でビルドする必要があります。 ただ、ビルドのためのスクリプト類は用意されてるので、簡単にビルドできます。

ビルド自体は以前試したここの手順の通りで、NetBox本体のリポジトリのtag名などを指定すれば、そのバージョンのイメージをビルドできます。

zaki-hmkc.hatenablog.com

Oracle Cloud A1.FlexのインスタンスへのDockerエンジン本体のインストールはこちら
Docker Composeのインストールはpip版Docker Plugin版どちらかでインストールする。本エントリではDocker PluginとしてインストールしたCompose v2でお試し。

Ubuntu 20.04 / Oracle Linux 8

$ git clone -b release https://github.com/netbox-community/netbox-docker.git
$ cd netbox-docker/

で、本来であれば必要に応じてdocker-compose.override.ymlを用意したりし、upすればデプロイできるのですが、

netbox-docker-netbox-1               | standard_init_linux.go:228: exec user process caused: exec format error
netbox-docker-netbox-1 exited with code 1

起動に失敗します。
これは、NetBoxのコンテナイメージがamd64向けしか公開されておらず、でも実行しようとしてるプラットフォームはarm64のため起動に失敗している状態のため、何をどう頑張っても起動できません。

f:id:zaki-hmkc:20210930230153p:plain

$ docker run --rm netboxcommunity/netbox:v3.0 
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
standard_init_linux.go:228: exec user process caused: exec format error

じゃあどうすればいいかと言うと、実行したいプラットフォーム用のイメージを自分でビルドすれば(今回動かしたいNetBoxについては)OKです。
NetBoxのコンテナイメージは、(わかりにくいけど)alpineがベースイメージになっていて、このイメージはarm64用が用意されているため、このイメージをベースにビルドします。特定プラットフォーム用のビルドというとクロスコンパイルみたいなイメージがありますが、単に動かしたいarm64のプラットフォーム(のDockerエンジン)上でビルドすればOKです。

f:id:zaki-hmkc:20210930230417p:plain

NetBoxのコンテナイメージビルドは、デプロイに使用するComposeファイルを提供しているnetbox-dockerリポジトリに、ビルド用のスクリプトも同梱しているため、そのスクリプトを実行すればよいので簡単にビルドできます。

github.com

2021.09.30時点で最新のv3.0.4でお試し。
ビルドスクリプトの引数に、NetBox本体のリポジトリのターゲットバージョンのtagを指定します。

github.com

$ ./build.sh v3.0.4

ビルドはこれだけ。そこそこ時間かかる。
ビルド完了すると以下の通り。

$ docker image ls
REPOSITORY               TAG           IMAGE ID       CREATED         SIZE
netboxcommunity/netbox   latest-ldap   955e60dabcfc   4 minutes ago   442MB
netboxcommunity/netbox   v3.0-ldap     955e60dabcfc   4 minutes ago   442MB
netboxcommunity/netbox   v3.0.4-ldap   955e60dabcfc   4 minutes ago   442MB
netboxcommunity/netbox   latest        cffca943100b   4 minutes ago   436MB
netboxcommunity/netbox   v3.0          cffca943100b   4 minutes ago   436MB
netboxcommunity/netbox   v3.0.4        cffca943100b   4 minutes ago   436MB

v3.0のタグを含めてビルドできてるので、そのままdocker compose upします。
(標準で用意されているdocker-compose.ymlv3.0タグ前提の記述なので)

$ docker compose up 
[+] Running 6/6
 ⠿ Container netbox-docker-redis-cache-1          Creat...                                         0.0s
 ⠿ Container netbox-docker-postgres-1             Created                                          0.0s
 ⠿ Container netbox-docker-redis-1                Created                                          0.0s
 ⠿ Container netbox-docker-netbox-housekeeping-1  Recreated                                        0.1s
 ⠿ Container netbox-docker-netbox-worker-1        Rec...                                           0.1s
 ⠿ Container netbox-docker-netbox-1               Recreated                                        0.1s
Attaching to netbox-docker-netbox-1, netbox-docker-netbox-housekeeping-1, netbox-docker-netbox-worker-1, netbox-docker-postgres-1, netbox-docker-redis-1, netbox-docker-redis-cache-1
netbox-docker-redis-cache-1          | 1:C 30 Sep 2021 12:11:13.783 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo

[...]

netbox-docker-netbox-1               | ✅ Initialisation is done.
netbox-docker-netbox-1               | ⏳ Waiting for control socket to be created... (1/10)
netbox-docker-netbox-1               | 2021/09/30 12:12:15 [warn] 7#7 Unit is running unprivileged, then it cannot use arbitrary user and group.
netbox-docker-netbox-1               | 2021/09/30 12:12:15 [info] 7#7 unit started
netbox-docker-netbox-1               | 2021/09/30 12:12:15 [info] 20#20 discovery started
netbox-docker-netbox-1               | 2021/09/30 12:12:15 [notice] 20#20 module: python 3.9.5 "/usr/lib/unit/modules/python3.unit.so"
netbox-docker-netbox-1               | 2021/09/30 12:12:15 [info] 7#7 controller started
netbox-docker-netbox-1               | 2021/09/30 12:12:15 [notice] 7#7 process 20 exited with code 0
netbox-docker-netbox-1               | 2021/09/30 12:12:15 [info] 22#22 router started
netbox-docker-netbox-1               | 2021/09/30 12:12:15 [info] 22#22 OpenSSL 1.1.1l  24 Aug 2021, 101010cf
netbox-docker-netbox-1               | ⚙️ Applying configuration from /etc/unit/nginx-unit.json
netbox-docker-netbox-1               | 2021/09/30 12:12:16 [info] 26#26 "netbox" application started
netbox-docker-netbox-1               | 🧬 loaded config '/etc/netbox/config/configuration.py'
netbox-docker-netbox-1               | 🧬 loaded config '/etc/netbox/config/extra.py'
netbox-docker-netbox-1               | 🧬 loaded config '/etc/netbox/config/logging.py'
netbox-docker-netbox-1               | 🧬 loaded config '/etc/netbox/config/plugins.py'
netbox-docker-netbox-1               | ✅ Unit configuration loaded successfully
netbox-docker-netbox-1               | 2021/09/30 12:12:18 [notice] 7#7 process 18 exited with code 0

はい。
あとはセキュリティグループなどの設定を確認しつつwebアクセスすればこの通り。

f:id:zaki-hmkc:20210930230721p:plain

Oracle Linux 7.9の場合

Gitバージョンでトラップあるので注意。
それ以外はUbuntu 20.04 / Oracle Linux 8と同じ。

$ ./build.sh v3.0.4
▶️ ./build.sh v3.0.4
🌐 Checking out 'v3.0.4' of NetBox from the url 'https://github.com/netbox-community/netbox.git' into '.netbox'
Note: checking out '84d83fbd143f26097db9464a46657b7a65ab27cb'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

fatal: git fetch-pack: expected shallow list

標準リポジトリyum installできるGitのバージョンが1系のため、必要な操作ができないエラーになります。

$ git version
git version 1.8.3.1

IUSのリポジトリからインストールできたら簡単かな?と思ったけど、あいにくこのリポジトリではarm64版のパッケージはありません。
探した結果、Oracle Linux 7.9では標準リポジトリrh-git218-git-all.noarchというパッケージが用意されています。(名前…いいのかな、これw)

$ sudo yum install rh-git218-git-all

このパッケージをインストールすると、/opt/rh/rh-git218/root/usr/bin/gitにv2系のGitがインストールされます。

$ /opt/rh/rh-git218/root/usr/bin/git version
git version 2.18.4

PATH設定してリビルド

$ export PATH=/opt/rh/rh-git218/root/usr/bin:$PATH
$ ./build.sh v3.0.4
▶️ ./build.sh v3.0.4
/opt/rh/rh-git218/root/usr/libexec/git-core/git-remote-https: error while loading shared libraries: libcurl-httpd24.so.4: cannot open shared object file: No such file or directory
❌ Remote branch 'v3.0.4' not found in 'https://github.com/netbox-community/netbox.git'; Nothing to do

をしようとすると、更にエラー。
検索すると以下がヒット。

bugzilla.redhat.com

$ sudo ln -s /opt/rh/httpd24/root/usr/lib64/libcurl-httpd24.so.4 /lib64/

この状態で更にリビルトしようとすると、同じように以下のエラー

$ ./build.sh v3.0.4
▶️ ./build.sh v3.0.4
/opt/rh/rh-git218/root/usr/libexec/git-core/git-remote-https: error while loading shared libraries: libnghttp2-httpd24.so.14: cannot open shared object file: No such file or directory
❌ Remote branch 'v3.0.4' not found in 'https://github.com/netbox-community/netbox.git'; Nothing to do

これも同様に、

$ sudo ln -s /opt/rh/httpd24/root/usr/lib64/libnghttp2-httpd24.so.14 /lib64/

する。
今度こそリビルド。

$ ./build.sh v3.0.4

しばらく待てばビルド完了するはず。

$ docker image ls
REPOSITORY               TAG           IMAGE ID       CREATED              SIZE
netboxcommunity/netbox   latest-ldap   feae491462bb   41 seconds ago       442MB
netboxcommunity/netbox   v3.0-ldap     feae491462bb   41 seconds ago       442MB
netboxcommunity/netbox   v3.0.4-ldap   feae491462bb   41 seconds ago       442MB
netboxcommunity/netbox   latest        b746cc1f2603   About a minute ago   436MB
netboxcommunity/netbox   v3.0          b746cc1f2603   About a minute ago   436MB
netboxcommunity/netbox   v3.0.4        b746cc1f2603   About a minute ago   436MB

[...]

あとは同じようにdocker-compose.override.ymlを作成し、docker compose up -dでデプロイできます。

$ docker compose up -d
[+] Running 1/3
 ⠴ redis-cache Pulling                                                                                          1.6s
   ⠿ 552d1f2373af Already exists                                                                                0.0s
   ⠋ 08b93f529d04 Waiting                                                                                       0.1s
[+] Running 1/169 Waiting                                                                                       0.1s
 ⠦ redis-cache Pulling                                                                                          1.7s

[...]

現状だとHTTPアクセスになっており経路が暗号化されないのでその点は注意。
あくまでお試しということで、セキュリティグループで自分のIPアドレスでのみアクセスできるようにするなどフィルタした方がよい。

Docker PluginとしてDocker Compose (v2) のインストールとお試し実行 (arm64 / Oracle Cloud A1.Flex)

気が付いたらCompose v2がリリース版になってました。

github.com

これまでのようにdocker-composeコマンド単体でなく、Docker Pluginとして利用する形式になっています。
あとv1系のときはx86_64のバイナリしかなかったけど、arm64やs390xのバイナリも配布されるようになってるようです。
Linuxの場合は現状ではマニュアルインストールとなっており、必要なバイナリを所定のパスにダウンロードして利用します。

バイナリはReleaseページから。

github.com

インストール手順はDockerのドキュメント参照。 ただし、2021.9.29時点でまだバージョンがv2.0.0-rc.3になっているのでここをv2.0.0に置き換えて、アーキテクチャも環境に合わせる。今回はarm64で。

docs.docker.com

Dockerがインストールされてる状態で以下実行(arm64の場合)。

$ mkdir -p ~/.docker/cli-plugins/
$ curl -SL https://github.com/docker/compose/releases/download/v2.0.0/docker-compose-linux-arm64 -o ~/.docker/cli-plugins/docker-compose
$ chmod +x ~/.docker/cli-plugins/docker-compose

これでdocker composeが実行できるようになります。(docker-composeでなく)

$ docker compose version
Docker Compose version v2.0.0

サンプル用Composeファイルは以下の通り。

$ cat docker-compose.yml 
version: '3'
services:
  httpd:
    image: httpd
    ports:
    - 8080:80
$ docker compose up -d
[+] Running 6/6
 ⠿ httpd Pulled                                                                      4.9s
   ⠿ 896f18f54b28 Pull complete                                                      2.1s
   ⠿ 678f4022827b Pull complete                                                      2.9s
   ⠿ c18c6f886b61 Pull complete                                                      3.2s
   ⠿ 8f8bb463f1da Pull complete                                                      4.4s
   ⠿ 5590d606630f Pull complete                                                      4.5s
[+] Running 2/2
 ⠿ Network compose_default    Created                                                0.2s
 ⠿ Container compose-httpd-1  Started                                                0.6s
$ docker compose ps
NAME                COMMAND              SERVICE             STATUS              PORTS
compose-httpd-1     "httpd-foreground"   httpd               running             0.0.0.0:8080->80/tcp, :::8080->80/tcp

Oracle CloudのA1.FlexコンピュートインスタンスOracle Linux 8とUbuntu20.04で確認。
どちらも同じ手順で実行確認。

つい先日arm64向けバイナリがないということでpipを使ってインストールしたけど、v2がリリースバージョンになってるしこっちでよいかな。

zaki-hmkc.hatenablog.com