zaki work log

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

[Kubernetes] KustomizeでConfigMapを扱う (configMapGenerator)

KustomizeにおけるConfigMapの管理おためし。 configMapGeneratorを使用する。

kubectl.docs.kubernetes.io

KustomizeおよびConfigMapそのものについては以下も参照。

zaki-hmkc.hatenablog.com

zaki-hmkc.hatenablog.com

Secretについては2022.12.08記事作成

zaki-hmkc.hatenablog.com

ConfigMapリソースの作成

key=value形式をkusomization.yamlにインライン記述

resources:
- sample-http.yaml
namespace: sample
configMapGenerator:
- name: sample-configure
  literals:
  - KUSTOMIZE_SAMPLE_HOST=172.29.0.1
  - KUSTOMIZE_SMPALE_PORT=6443

生成されるConfigMapは以下。

$ kubectl get cm -n sample
NAME                          DATA   AGE
kube-root-ca.crt              1      11s
sample-configure-8fctf9658k   2      11s
kubectl get cm -n sample sample-configure-7fgcbkm8mg -o yaml
apiVersion: v1
data:
  KUSTOMIZE_SAMPLE_HOST: 172.29.0.1
  KUSTOMIZE_SMPALE_PORT: "6443"
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"KUSTOMIZE_SAMPLE_HOST":"172.29.0.1","KUSTOMIZE_SMPALE_PORT":"6443"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"sample-configure-8fctf9658k","namespace":"sample"}}
  creationTimestamp: "2022-11-20T06:37:06Z"
  name: sample-configure-8fctf9658k
  namespace: sample
  resourceVersion: "527167"
  uid: b021e399-b1f0-46d9-91ad-a344bfbd5c80

key=value形式のファイル参照

kustomization.yamlファイルのある場所基準で、以下のファイルを用意しておく。

$ cat config/sample.ini 
HOST=10.0.0.2
PORT=25

この状態で

resources:
- sample-http.yaml
namespace: sample
configMapGenerator:
- name: sample-configure
  envs:
  - config/sample.ini

生成されるConfigMapは以下の通り。

$ kubectl get cm -n sample
NAME                          DATA   AGE
kube-root-ca.crt              1      13m
sample-configure-d26ddgbkb6   2      84s
$ kubectl get cm -n sample sample-configure-d26ddgbkb6 -o yaml
apiVersion: v1
data:
  HOST: 10.0.0.2
  PORT: "25"
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"HOST":"10.0.0.2","PORT":"25"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"sample-configure-d26ddgbkb6","namespace":"sample"}}
  creationTimestamp: "2022-11-20T05:16:05Z"
  name: sample-configure-d26ddgbkb6
  namespace: sample
  resourceVersion: "525398"
  uid: 5a289dcc-9af5-48b5-a71d-d4a5f168145e

ファイルそのものをConfigMap化

ミドルウェアの設定ファイルのように、ConfigMapリソースのマニフェストkustomization.yamlファイルへのインライン記述でなく、ファイル単独でバージョン管理したい場合などは、

$ cat config/sample.config 
this is sample file.

このようにconfig/sample.configファイルがある状態で、以下のkustomization.yamlを使ってデプロイする。

resources:
- sample-http.yaml
namespace: sample
configMapGenerator:
- name: sample-configure
  files:
  - config/sample.config

生成されるConfigMapは以下の通り。

$ kubectl get cm -n sample 
NAME                          DATA   AGE
sample-configure-cch6ttcgtb   1      4m52s
kube-root-ca.crt              1      4m52s
$ kubectl get cm -n sample sample-configure-cch6ttcgtb -o yaml
apiVersion: v1
data:
  sample.config: |
    this is sample file.
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"sample.config":"this is sample file.\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"sample-configure-cch6ttcgtb","namespace":"sample"}}
  creationTimestamp: "2022-11-20T05:03:55Z"
  name: sample-configure-cch6ttcgtb
  namespace: sample
  resourceVersion: "525139"
  uid: 5a4e9bd2-9452-41b1-8db1-552e9fd084ab

ConfigMapリソースの参照

Kustomizeで生成したConfigMapにはランダムなsuffixが付与されるが、nameで指定したConfigMap名を指定しておけば、Kustomizeがデプロイ時によしなにしてくれる。

環境変数として参照

Pod側の参照設定は、Deploymentリソースなどの定義で従来通りenvFromフィールド以下でconfigMapRefを使用して行う。
次のような感じで、kustomization.yamlのconfigMapGeneratorで指定するConfigMap名と同じリソース名を指定すればOK。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: sample-http
  name: sample-http
  namespace: kustomize-sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-http
  template:
    metadata:
      labels:
        app: sample-http
    spec:
      containers:
      - image: httpd
        name: httpd
        envFrom:
        - configMapRef:
            name: sample-configure   # <- これ
resources:
- sample-http.yaml
namespace: sample
configMapGenerator:
- name: sample-configure             # <- これ
  literals:
  - SAMPLE_HOST=172.29.0.1
  - SMPALE_PORT=6443

この内容でKustomizeを使ってデプロイすると以下の通り、生成されたConfigMapのsuffixも自動で付与される。

$ kubectl get pod -n sample 
NAME                           READY   STATUS    RESTARTS   AGE
sample-http-7cfb94f7d4-f7d65   1/1     Running   0          92s
sample-http-7cfb94f7d4-z4js5   1/1     Running   0          92s
$ kubectl get pod -n sample sample-http-7cfb94f7d4-f7d65 -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2022-11-20T06:37:06Z"
  generateName: sample-http-7cfb94f7d4-
  labels:
    app: sample-http
    pod-template-hash: 7cfb94f7d4
  name: sample-http-7cfb94f7d4-f7d65
  namespace: sample
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: sample-http-7cfb94f7d4
    uid: d3021a2a-823c-4620-bfa3-156f53e49634
  resourceVersion: "527195"
  uid: 62438100-d305-44f5-b50d-5b5293280f36
spec:
  containers:
  - envFrom:
    - configMapRef:
        name: sample-configure-8fctf9658k
    image: httpd
    imagePullPolicy: Always
    name: httpd
  :
  :
status:
 :
 :

Pod内の環境変数を確認すると以下の通り。

$ kubectl exec -it -n sample sample-http-7cfb94f7d4-f7d65 -- env | sort
:
KUSTOMIZE_SAMPLE_HOST=172.29.0.1
KUSTOMIZE_SMPALE_PORT=6443
:

ファイルシステムへマウント

コンテナ内にファイルシステムとしてマウントするには、これも通常のマニフェストと同様にvolumesフィールドでconfigMapを指定する。
Deploymentの定義としては以下のような感じ。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: sample-http
  name: sample-http
  namespace: kustomize-sample
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-http
  template:
    metadata:
      labels:
        app: sample-http
    spec:
      containers:
      - image: httpd
        name: httpd
        volumeMounts:
        - name: config-volume
          mountPath: /opt
      volumes:
      - name: config-volume
        configMap:
          name: sample-configure   # <- これ
resources:
- sample-http.yaml
namespace: sample
configMapGenerator:
- name: sample-configure           # <- これ
  files:
  - config/sample.config

マウントしたファイルは以下の通り。

$ kubectl get pod -n sample 
NAME                          READY   STATUS    RESTARTS   AGE
sample-http-99587f5b9-dt8zk   1/1     Running   0          7m15s
sample-http-99587f5b9-k8pl8   1/1     Running   0          7m15s
$ kubectl exec -it -n sample sample-http-99587f5b9-dt8zk -- ls /opt
sample.config
$ kubectl exec -it -n sample sample-http-99587f5b9-dt8zk -- cat /opt/sample.config
this is sample file.

リソース名のランダムな文字

デフォルトの動作では生成されるConfigMapリソース名の末尾にsuffixが付与される。
一見ランダムだけど、内容が同じConfigMapであれば同じハッシュ値が付与される…ように見える。(仕様未確認)

この動作は、例えばミドルウェアの設定ファイル更新のような「ファイルが更新されただけではダメでアプリケーションのプロセスの再起動が必要」というケースで都合がよく、Kustomizeを使ってConfigMapの更新が行われConfigMapリソース名も変更されることによって、Podの再生成までが自動で行われる。

それに対してhtmlコンテンツや画像のようにファイルが更新されてもプロセス再起動は不要な場合は、ConfigMapの更新によってPodも再作成されるのは効率が悪い。

このsuffixを付与する動作を抑制するには、オプションのdisableNameSuffixHashを指定する。

resources:
- sample-http.yaml
namespace: sample
configMapGenerator:
- name: sample-configure
  files:
  - config/sample.config
  options:
    disableNameSuffixHash: true

これでsuffixが付与されずにkusomization.yamlで指定したConfigMap名がそのままリソース名となる。
suffixが付与されないため、ConfigMapの内容を変更して再度Kustomizeを実行しても、ConfigMapリソースを参照しているPodは再生成されなくなる。

$ kubectl get cm -n sample
NAME               DATA   AGE
sample-configure   1      7s
kube-root-ca.crt   1      7s

環境

k3s v1.24を使用

$ kubectl version --short
Client Version: v1.25.3
Kustomize Version: v4.5.7
Server Version: v1.24.3+k3s1

[Kubernetes] Kustomize事始め

Helmあるから別にいいかーと避けていたkustomize、仕事で触れる機会があったのでごくごく簡単にまとめ。

kubectl.docs.kubernetes.io

※ Kustomizeの用途でよくあるbaseとoverlayを使った環境(dev/stg/prd)ごとのパラメタセットの切り替えは本エントリではまだ扱いません

基本

かじった程度だけど、つかんだ感触としては以下の特徴。(個人の感想)

  • kustomizeの定義ファイルはkustomization.yaml
  • 既存の静的なマニフェストファイルに対して、kustomization.yamlで定義した内容にしたがってパラメタを上書きする
    • Helmなどはパラメタ化したい箇所を変数参照する書式にするが、kustomizeではその必要はない
  • Helmのようにパブリックなアプリケーションのリポジトリは多分なさそう
  • kubectlコマンドに内蔵してるので追加コマンド等のインストール不要ですぐに使える

という感じで、小規模なシステムやプロジェクトで、マニフェストファイルのパラメタ化を行いたいときは結構ハマると思う。
ポイントは元のマニフェストファイルを変更することなく特定の値をカスタムするという点で、雰囲気としては「既存のマニフェストファイルに対して値を変更するフィルタをかます」という動作が近いかも。(多分)

使ってみる

kustomization.yamlの作成

最低限必要な記述は、kustomization.yamlが処理する対象マニフェストファイルのパス。
ファイルのパスはkustomization.yamlがある場所からの相対パスで記述できる。

kustomization.yamlファイルのある場所にmanifestディレクトリを作り、その配下にsample-app.yamlファイルとsample-db.yamlファイルがある場合は以下のようになる。

resources:
- manifest/sample-app.yaml
- manifest/sample-db.yaml

また、ファイルシステム上にあるマニフェストファイルだけでなく、リポジトリ上にkustomization.yamlファイルがあればGitのリモートリポジトリの指定も可能。
その場合は以下のような書式。(リポジトリ上のファイル構成については本エントリでは扱わない)
詳細についてはresourcesのページと、そこからリンクされているhashicorp URL formatを参照。

resources:
- github.com/kubernetes-sigs/kustomize/examples/helloWorld?ref=v3.3.1

github.com

デプロイの確認と実行

クラスタへリソースをデプロイするにはapplyサブコマンドで-kオプションを使用する。実行するコマンド例は以下。

kubectl apply -k <kustomization.yamlのあるディレクトリパス>

クラスタへのリソースのデプロイでなく、Kustomizeによって変化する最終的なマニフェストの内容を確認するには、サブコマンドkustomizeを使用する。実行するコマンド例は以下。

kubectl kustomize <kustomization.yamlのあるディレクトリパス>

Kustomizeによるマニフェストの書き換え

全部を紹介はできないので、利用頻度が高そうで簡単なものを紹介

サンプルとして以下の何もしないwebサーバーをデプロイするだけのマニフェストを使用。

github.com

curl -LO https://raw.githubusercontent.com/zaki-lknr/k8s-samples/master/sample-web/httpd-loadbalancer-namespace/sample-http.yaml

ゲットしたら同じディレクトリにkustomization.yamlを作成する。

resources:
- sample-http.yaml

イメージのタグを変更

提供されているマニフェストに記載されているイメージのタグを変更したい場合。デフォルトではイメージの最新バージョンがタグとして使用されているが、1世代前のバージョンを使用したいなど。
元のマニフェストファイルを変更することなく、kustomization.yamlの記述で変更ができる。

サンプルではhttpdイメージのタグの指定がない(=latest)ので、2.4を指定するように定義してみる。
その場合のkustomization.yamlの内容は以下。

resources:
- sample-http.yaml

images:
- name: httpd
  newTag: "2.4"

nameにはタグを上書きする対象を探すために、マニフェストで指定してあるイメージ(image)を指定する(nameでなく…サンプルはどちらもhttpdでわかりにくいけど)。
newTagに上書きしたいタグ名を指定する。注意点として文字列型で指定しなければならないため、タグ名が数字とドットのみのバージョン番号形式の場合はクォートも必要。

イメージのURL変更

ユースケースとしては、公開されているマニフェストファイルはDocker Hubにあるパブリックなイメージを使用するようになっているが、運用ではカスタムビルドしたものをプライベートにあるコンテナレジストリに配置して利用したい、みたいな場合。

サンプルでは単にhttpdと記述しておりDockerHubからpullしてくる動作になっているが、これを例としてQuay.ioのhttpdイメージをpullするよう変更してみる。
その場合のkustomization.yamlの内容は以下。

resources:
- sample-http.yaml

images:
- name: httpd
  newName: quay.io/fedora/httpd-24:2.4

イメージのURL変更も、タグと同様imagesフィールドを使用。nameで対象を指定し、書き換えるURLをnewNameに指定する。

ネームスペースの指定

マニフェストにはネームスペースを明記せず、kubectl applyで適用する際に-n <namespace name>で指定するパターンは多いと思うが、kustomization.yamlにデプロイするネームスペースを指定することができる。

またこの際、指定したネームスペースが存在しない場合はエラーになるが、ネームスペースを作成するマニフェストも含めている場合は、作成するネームスペースもデプロイするネームスペースも全てKustomizeで上書きできる。

resources:
- sample-http.yaml

namespace: myapps

各リソース定義のmetadata.namespaceおよび、kind: Namespacemetadata.nameのネームスペースが、namespaceフィールドに指定したネームスペース名に書き変わってデプロイされる。

レプリカ数の指定

Deploymentのマニフェストで指定されているPodのレプリカ数をkustomization.yamlで上書き設定できる。

resources:
- sample-http.yaml

replicas:
- name: sample-http
  count: 1

nameに対象のリソースを指定し、レプリカ数をcountで指定する。対象リソースはimageフィールドのコンテナイメージではなく、Deploymentなどのリソース名を指定する。
確認してないけどReplicaSetStatefulSetにも使用可能。

kustomizeコマンド

kubectlコマンド内蔵のKustomizeでなく、単体のバイナリをインストールして実行もできる。kubectlのアップデートでKustomizeのバージョンも変更されるとCIなどで都合が悪い場合など、バージョンを固定したい場合に利用できる。

なお、以前は「kubectl内蔵のkustomizeはバージョンが古い」という時期があったが、現在は最新バージョンが内蔵されるようになっているので、カジュアルに使いたいレベルであれば、kustomizeコマンドを別途入れなくても良い。

インストール

kustomizeコマンドのインストールはいくつか方法はあるが、素の環境であればバイナリインストールが一番簡単。

kubectl.docs.kubernetes.io

curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash

実行するとカレントディレクトリに実行ファイルが配置されるので、必要に応じて/usr/local/bin辺りに置く。


ちなみに現バージョンではドキュメントにもある通りARMアーキテクチャのOSだとこのスクリプトは正しく動作しないので、kustomizeのリリースページからバイナリを直接ダウンロードする。

This script doesn’t work for ARM architecture. If you want to install ARM binaries, please go to the release page to find the URL.

もしくはスクリプトをダウンロードして、archを判定しているロジックに

aarch64)
    arch=arm64
    ;;

を付け足しても動く。
(uname -mの結果がaarch64の場合の判定が無いため、デフォルトのamd64として動作している)


バージョンを指定してインストールする場合は、引数にインストールしたいバージョンを指定する。
間違いが無いのはインストールスクリプトを一度ダウンロードして実行すると良い。

./install_kustomize.sh 4.5.5

curlで取得したスクリプトを直接実行する場合は、プロセス置換を使って以下のように実行すればインストールできる。

[zaki@cloud-dev2 tmp]$ bash <(curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh") 4.5.5
{Version:kustomize/v4.5.5 GitCommit:daa3e5e2c2d3a4b8c94021a7384bfb06734bcd26 BuildDate:2022-05-20T20:25:40Z GoOs:linux GoArch:amd64}
kustomize installed to /home/zaki/local/tmp/kustomize

使い方

kustomizeコマンドはマニフェストの値を書き換えるフィルタ機能に限定している(クラスタへのリソース作成は具備していない)ため、標準出力へのリソース書き換え結果をkubectl apply -fへリダイレクトして使用する。

値の確認

kubectl kustomize <dir>に相当するのは以下。

kustomize build <dir>

リソースのデプロイ

kubectl apply -k <dir>に相当するのは以下。

kustomize build <dir> | apply -f -

その他

他にもkustomization.yamlのスケルトンを出力するkustomize createなどいくつかのサブコマンドがある。
詳細はヘルプ参照。

kubectl.docs.kubernetes.io

環境

$ kubectl version --short
Flag --short has been deprecated, and will be removed in the future. The --short output will become the default.
Client Version: v1.25.3
Kustomize Version: v4.5.7
Server Version: v1.24.3+k3s1

configMapGeneratorpatchesあたりは追加で早めにまとめたいな…

11/20: configMapGeneratorは書いた

[Ansible] 任意のタイミングでダイナミックインベントリのホスト情報収集を行う

Ansibleで(CFnなどのモジュールを使って、も含む)クラウドリソースを作成し、後続のタスクで作成したコンピュートインスタンス(EC2)へ接続して処理を行いたい、みたいなケースで、ダイナミックインベントリを使用することでタグなどを使ってEC2情報を動的に取得してアクセスできます。

……と仕組み的には可能なんだけど、1回のAnsible実行ではAnsible起動時にダイナミックインベントリによるホスト情報を収集するため、「クラウドリソース作成後のEC2アクセス」のタイミングではダイナミックインベントリによって取得されるホスト情報は空(正確にはAnsible起動時に収集したリソース作成前の情報)のまま。
ではどうすれば作成したばかりのEC2へのアクセスを実現できるかと言うと、任意のタイミングでインベントリを再読み込みすることができるansible.builtin.metaモジュールrefresh_inventoryパラメタを使ったタスクを実装することで、クラウドリソースを作成したあとにダイナミックインベントリによるホスト情報の収集を行えます。

docs.ansible.com

具体的には以下の通り。

- name: refresh inventory
  ansible.builtin.meta: refresh_inventory

このタスクが実行されると、そのタイミングでインベントリの再読み込みが行われるため、ダイナミックインベントリを使ったクラウドリソースのホスト情報収集が行われ、「後続のタスクで作成したコンピュートインスタンス(EC2)へ接続して処理を行う」を実現できます。

全体のplaybook構成としては、以下のようなイメージでEC2の作成とEC2への処理の自動化をまとめて実装できます。

- hosts: localhost
  tasks:
  - クラウドリソースを作成するタスク
  - ansible.builtin.meta: refresh_inventory

- hosts: ダイナミックインベントリで取得したホストグループ
  tasks:
  - EC2への操作
  - :
    :
    :

参考:

metaモジュールを使ったその他の操作

zaki-hmkc.hatenablog.com

「任意のタイミングで〇〇する」のgather_facts編

zaki-hmkc.hatenablog.com

AWSのダイナミックインベントリ

zaki-hmkc.hatenablog.com

そういえばダイナミックインベントリを使ってEC2に接続してタスク実行はまだまとめてなかったですね。。

(作業ログ) RHEL 8.5へのDocker 20.10インストール

諸事情でRHELでPodmanでなくDockerを使う必要があったため、インストールした時の作業ログ。

状況

speakerdeck.com

そして現在(Docker 20.10)の状況としては、RHEL向けのパッケージはないけれど、CentOS 8用であればバイナリ互換の他のOSでも動くらしい…ということでRHEL8.5へのインストールを確認してみた。

logmi.jp

環境

[zaki@rhel8 ~]$ cat /etc/redhat-release 
Red Hat Enterprise Linux release 8.5 (Ootpa)

手順

docs.docker.com

以下は、このリンクにのってる手順通りに実施した作業とそのログ。

Dockerのインストール

古いバージョンの削除

[zaki@rhel8 ~]$ sudo yum remove docker \
>                   docker-client \
>                   docker-client-latest \
>                   docker-common \
>                   docker-latest \
>                   docker-latest-logrotate \
>                   docker-logrotate \
>                   docker-engine
サブスクリプション管理リポジトリーを更新しています。
一致した引数がありません: docker
一致した引数がありません: docker-client
一致した引数がありません: docker-client-latest
一致した引数がありません: docker-common
一致した引数がありません: docker-latest
一致した引数がありません: docker-latest-logrotate
一致した引数がありません: docker-logrotate
一致した引数がありません: docker-engine
削除対象のパッケージはありません。
依存関係が解決しました。
行うべきことはありません。
完了しました!

リポジトリのセットアップ

[zaki@rhel8 ~]$ sudo yum install -y yum-utils
サブスクリプション管理リポジトリーを更新しています。
メタデータの期限切れの最終確認: 1:49:22 時間前の 2022年10月04日 11時41分55秒 に実施しました。
パッケージ yum-utils-4.0.21-4.el8_5.noarch は既にインストールされています。
依存関係が解決しました。
=================================================================================================================================
 パッケージ                         アーキテクチャー バージョン                    リポジトリー                            サイズ
=================================================================================================================================
アップグレード:
 dnf                                noarch           4.7.0-8.el8                   rhel-8-for-x86_64-baseos-rpms           541 k
 dnf-data                           noarch           4.7.0-8.el8                   rhel-8-for-x86_64-baseos-rpms           155 k
 dnf-plugins-core                   noarch           4.0.21-11.el8                 rhel-8-for-x86_64-baseos-rpms            71 k
 json-c                             x86_64           0.13.1-3.el8                  rhel-8-for-x86_64-baseos-rpms            41 k
 libdnf                             x86_64           0.63.0-8.2.el8_6              rhel-8-for-x86_64-baseos-rpms           706 k
 python3-dnf                        noarch           4.7.0-8.el8                   rhel-8-for-x86_64-baseos-rpms           545 k
 python3-dnf-plugins-core           noarch           4.0.21-11.el8                 rhel-8-for-x86_64-baseos-rpms           240 k
 python3-hawkey                     x86_64           0.63.0-8.2.el8_6              rhel-8-for-x86_64-baseos-rpms           117 k
 python3-libdnf                     x86_64           0.63.0-8.2.el8_6              rhel-8-for-x86_64-baseos-rpms           778 k
 yum                                noarch           4.7.0-8.el8                   rhel-8-for-x86_64-baseos-rpms           202 k
 yum-utils                          noarch           4.0.21-11.el8                 rhel-8-for-x86_64-baseos-rpms            73 k

トランザクションの概要
=================================================================================================================================
アップグレード  11 パッケージ

ダウンロードサイズの合計: 3.4 M
[zaki@rhel8 ~]$ sudo yum-config-manager \
>     --add-repo \
>     https://download.docker.com/linux/centos/docker-ce.repo
サブスクリプション管理リポジトリーを更新しています。
repo の追加: https://download.docker.com/linux/centos/docker-ce.repo

Dockerのインストール

[zaki@rhel8 ~]$ sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin
サブスクリプション管理リポジトリーを更新しています。
Docker CE Stable - x86_64                                                                                                         497 kB/s |  27 kB     00:00    
依存関係が解決しました。
==================================================================================================================================================================
 パッケージ                              アーキテクチャー  バージョン                                           リポジトリー                                サイズ
==================================================================================================================================================================
インストール:
 containerd.io                           x86_64            1.6.8-3.1.el8                                        docker-ce-stable                             33 M
 docker-ce                               x86_64            3:20.10.18-3.el8                                     docker-ce-stable                             21 M
 docker-ce-cli                           x86_64            1:20.10.18-3.el8                                     docker-ce-stable                             30 M
 docker-compose-plugin                   x86_64            2.10.2-3.el8                                         docker-ce-stable                            7.2 M
依存関係のインストール:
 container-selinux                       noarch            2:2.188.0-1.module+el8.6.0+15917+093ca6f8            rhel-8-for-x86_64-appstream-rpms             59 k
 docker-ce-rootless-extras               x86_64            20.10.18-3.el8                                       docker-ce-stable                            4.6 M
 fuse-overlayfs                          x86_64            1.9-1.module+el8.6.0+15917+093ca6f8                  rhel-8-for-x86_64-appstream-rpms             73 k
 fuse3                                   x86_64            3.2.1-12.el8                                         rhel-8-for-x86_64-baseos-rpms                50 k
 fuse3-libs                              x86_64            3.3.0-15.el8                                         rhel-8-for-x86_64-baseos-rpms                95 k
 libslirp                                x86_64            4.4.0-1.module+el8.6.0+15875+dc9a2b96                rhel-8-for-x86_64-appstream-rpms             70 k
 policycoreutils-python-utils            noarch            2.9-16.el8                                           rhel-8-for-x86_64-baseos-rpms               252 k
 slirp4netns                             x86_64            1.2.0-2.module+el8.6.0+15917+093ca6f8                rhel-8-for-x86_64-appstream-rpms             54 k
弱い依存関係のインストール:
 docker-scan-plugin                      x86_64            0.17.0-3.el8                                         docker-ce-stable                            3.8 M
モジュールストリームの有効化中:
 container-tools                                           rhel8                                                                                                 

トランザクションの概要
==================================================================================================================================================================
インストール  13 パッケージ

ダウンロードサイズの合計: 100 M
インストール後のサイズ: 389 M
これでよろしいですか? [y/N]: 

Dockerの開始

[zaki@rhel8 ~]$ sudo systemctl enable --now docker
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service → /usr/lib/systemd/system/docker.service.
[zaki@rhel8 ~]$ sudo systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2022-10-04 13:39:51 JST; 3s ago
     Docs: https://docs.docker.com
 Main PID: 19283 (dockerd)
    Tasks: 8
   Memory: 24.9M
   CGroup: /system.slice/docker.service
           └─19283 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

:
:

動作確認

[zaki@rhel8 ~]$ sudo docker run -p 8080:80 -d httpd
Unable to find image 'httpd:latest' locally
latest: Pulling from library/httpd
31b3f1ad4ce1: Pull complete 
f29089ecfcbf: Pull complete 
a9fcd580ef1c: Pull complete 
a19138bf3164: Pull complete 
5bfb2ce98078: Pull complete 
Digest: sha256:71e882df50adc606c57e46e5deb3c933288e2c7775472a639326d9e4e40a47c2
Status: Downloaded newer image for httpd:latest
8ff93381edc8c3924d5eeb0d15d430c9296ab1cdde39176e23019e155c582d82
[zaki@rhel8 ~]$ curl localhost:8080
<html><body><h1>It works!</h1></body></html>

ということで、問題なく動きました。
docker composeもOKです。

[zaki@rhel8 ~]$ docker --version
Docker version 20.10.18, build b40c2f6
[zaki@rhel8 ~]$ docker compose version
Docker Compose version v2.10.2

[AWX / AAP] ジョブの実行をリモートのKubernetesクラスタで行う

デフォルトでは、ジョブの実行はAWXをデプロイしているKubernetesクラスタ上でpodがデプロイされて行われる。設定によって、podをデプロイするクラスタを、異なるクラスタ(AWXをデプロイしていないクラスタ)へ変更することができる。

本記事内では便宜上、AWXをデプロイしているクラスタを「ローカルクラスタ」、ジョブ実行のpodをデプロイするクラスタを「リモートクラスタ」と称する。(この記事内のローカルルール)

  • 2022.08.25: エラーパターンを追記

リモートクラスタ設定

ローカルクラスタからの接続に必要なリソースを作成する。
以下も参照。

アクセス用ServiceAccountの作成

ローカルクラスタからリモートクラスタへの接続用のServiceAccountを作成する。
AWXからはこのServiceAccountのトークンを使って認証してpodをデプロイする、という動作。

ServiceAccountに対するロールの設定は暫定(不要なものもついてる)ので、運用のときは必要最小限の権限にすることを検討すること。

NamespaceとServiceAccountの作成は以下の通り。
このNamespaceはジョブのpodをデプロイする場所になる。

[zaki@k8s-master ~]$ kubectl create namespace awx-exec
namespace/awx-exec created
[zaki@k8s-master ~]$ kubectl create serviceaccount awx-user -n awx-exec
serviceaccount/awx-user created

Roleの作成と、ServiceAccountへのRoleBinding作成は以下。(ロールの割り当て)

[zaki@k8s-master ~]$ kubectl create role awx-role -n awx-exec --verb=* --resource=* 
role.rbac.authorization.k8s.io/awx-role created
[zaki@k8s-master ~]$ kubectl create rolebinding awx-rolebind -n awx-exec --role=awx-role --serviceaccount=awx-exec:awx-user
rolebinding.rbac.authorization.k8s.io/awx-rolebind created

CLIでやるとこのとおりだけど、マニフェストなら以下の通り。

---
apiVersion: v1
kind: Namespace
metadata:
  name: awx-exec
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: awx-user
  namespace: awx-exec
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: awx-role
  namespace: awx-exec
rules:
- apiGroups:
  - ""
  resources:
  - '*'
  verbs:
  - '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: awx-rolebind
  namespace: awx-exec
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: awx-role
subjects:
- kind: ServiceAccount
  name: awx-user
  namespace: awx-exec

アクセストークンの取得

Kubernetes v1.24以降は以下のコマンドで。
出力は1行になっていて、この内容をAWXに登録する。

[zaki@k8s-master ~]$ kubectl create token awx-user -n awx-exec 
eyJhbGciOiJ ......

zaki-hmkc.hatenablog.com

v1.23以前のSecretリソース内のトークンを使う場合は、以下の「APIトークン指定」の項を参照。

zaki-hmkc.hatenablog.com

AWXの設定

AWXでリモートクラスタでジョブを実行するための設定。

認証情報の作成

リモートクラスタへの接続情報は、「認証情報」で設定を作成する。

名前は適当に入力し、「認証情報タイプ」は「OpenShift または Kubernetes API Bearer トークン」を選択する。

追加の入力項目に、APIサーバーのアドレス、作成したServiceAccountのトークン、クラスタの証明書を入力する。

エンドポイントはAPIサーバーのアドレスを入力。kubectlコマンドが使えるならkubectl cluster-infoでも確認できる。

[zaki@k8s-master ~]$ kubectl cluster-info
Kubernetes control plane is running at https://k8s.example.org:6443  # <- これ
CoreDNS is running at https://k8s.example.org:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

証明書は、kubeconfigファイル(デフォルトで$HOME/.kube/config)からcluster以下にあるcertificate-authority-dataの内容をbase64デコードするか、クラスタ上の各Namespaceにあるkube-root-ca.crtConfigMapから抜き出す。
ConfigMapから取得する場合は以下のコマンドを使えば簡単に取り出せる。(-o yamlの結果の場合は行頭にスペースがあるので削る。スペースがあるとエラーになる)

[zaki@k8s-master ~]$ kubectl get cm kube-root-ca.crt -o jsonpath='{.data.ca\.crt}'
-----BEGIN CERTIFICATE-----
[snip]
-----END CERTIFICATE-----

もしエンドポイントがIPアドレス形式だったり証明書の検証ができない場合は、下部の「SSLの検証」のチェックを外す。(その際証明書データは不要…と思う)

インスタンスグループの追加

ここから実行ノードの追加の設定。
メニューの「インスタンスグループ」で「追加」押下。

AAPの場合は「コンテナ―グループの追加」と「インスタンスグループの追加」の2択になるので、コンテナ―グループを選択。

新規コンテナグループの作成画面になるので、適当な名称を入力し、「Pod 仕様のカスタマイズ」をチェックする

Pod仕様のカスタマイズフィールドが表示されるので、以下を修正する。

  • metadata.namespace: デプロイするNamespace名。前述の例の場合awx-exec
  • spec.serviceAccountName: トークン取得に使用したServiceAccount名。前述の例の場合awx-user

これでリモートクラスタ自体の設定と、リモートクラスタへの接続情報の設定が完了。

ジョブの設定

ここから、リモートクラスタで実行したいジョブに、クラスタの指定を行う。
といってもジョブテンプレートの「インスタンスグループ」に追加設定するだけ。

🔍アイコンを押下し、先ほど作ったインスタンスグループを選択して、ジョブテンプレートを保存すればOK

実行

準備ができたらジョブテンプレートを普通に実行。
このとき、リモートクラスタの方で-w (watch)を付加してpodの状態を見ておくと、ジョブのpodがデプロイ・実行される状況を確認できる。
初回はイメージのダウンロードが必要なので時間がかかるが、2度目からすぐ実行(Running状態)になる。

[zaki@k8s-master ~]$ kubectl get pod -n awx-exec -w
NAME                     READY   STATUS    RESTARTS   AGE
automation-job-3-rgjqz   0/1     Pending   0          0s
automation-job-3-rgjqz   0/1     Pending   0          0s
automation-job-3-rgjqz   0/1     ContainerCreating   0          0s
automation-job-3-rgjqz   0/1     ContainerCreating   0          0s
automation-job-3-rgjqz   1/1     Running             0          2m31s
automation-job-3-rgjqz   1/1     Terminating         0          2m39s

...

AWXのUIでもこの通りジョブの実行を確認できる。

エラーのパターン

設定不備などで発生するエラーの内容メモ。

トークンが不正

トークンの期限が切れていたり、誤っていると発生。

Error creating pod: Unauthorized

NamespaceやServiceAccountが存在しない

インスタンスグループの「Pod仕様のカスタマイズ」で入力したNamespaceやServiceAccountが存在しない場合は、トークン不正と同じUnauthorizedエラーが出力された。

Error creating pod: Unauthorized

証明書が不正

誤った証明書やSSL検証ができない場合に発生。
SSLの検証」をオフにすれば回避できる

Error creating pod: Post "https://k8s.example.org:6443/api/v1/namespaces/awx-exec/pods": x509: certificate signed by unknown authority

ServiceAccountに権限が足りない

bindしたroleに、podをデプロイする権限がないと発生。

Error creating pod: pods is forbidden: User "system:serviceaccount:awx-exec:awx-user" cannot create resource "pods" in API group "" in the namespace "awx-exec"

実行環境のイメージの取得に失敗

実行環境として指定したイメージがリポジトリ上に無い場合や、認証が必要なリポジトリなのにその認証に失敗した場合などで発生。

Error creating pod: container failed to start, ImagePullBackOff

ImagePullBackOffの詳細は、Kubernetesクラスタ側で kubectl get eventを実行して詳細を確認できる。

例えばイメージのpullに認証が必要なのに認証できていない場合は以下。

7m47s       Warning   Failed      pod/automation-job-28-jz6mk   Failed to pull image "zakihmkc/priv-httpd:latest": rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/zakihmkc/priv-httpd:latest": failed to resolve reference "docker.io/zakihmkc/priv-httpd:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed

環境

  • AWX: 21.3.0
  • AAP: 2.1.1 (Automation Controller 4.1.1)
    • on RHEL8.6
  • リモートクラスタ: Kubernetes 1.24.3 (kubeadmで構築)

Ubuntu 22.04をDVDブートしてAutoinstallで自動インストール (TFTP/PXEブート無し)

ググるPXEブートしてネットワークインストールの情報はたくさんあるけど、DVDブートからAutoinstallする情報はみあたらなかったのでお試し(というか自動インストールというとkickstartしか知らずDebian/Ubuntu系ってどうするんだろう、という状態でしたw)。
PXEブートするためのTFTPサーバーの設定とかその辺不要なので、簡易的に自動インストールしたい場合に構成を小さくできる。
PXE/TFTPのかわりにAutoinstallを使うためのオプション指定を起動時に手入力する、というもの。
ただし、Autoinstallするためのuser-dataファイルを配布するためのwebサーバーは別途用意する。

kickstartインストールでDVDブートして起動オプションでinst.ks=http://kickstart.example.org/path/to/install.cfgを追加してインストールするのと同じ要領。

構成図

こんな感じ。あとwebサーバーへ疎通の際のIP割り当てのためのDHCPも必要(ブロードバンドルータとかのやつでOK)

今回の環境はESXi上のVMでAutoinstallをお試し。インストールメディアをドライブにセットし、普通に起動する方式。

user-dataファイルの用意

Autoinstallにおいて、kickstartインストールにおけるcfgファイルに相当するのがuser-dataファイル。
このファイルの一番簡単(当社比)な入手方法は「手動で一度OSインストールを行い、その時のインストールパラメタが記録されたファイルを使う」で、一度Ubuntuをインストールすると/var/log/installer/autoinstall-user-dataにファイルが作成される。RHEL系の/root/anaconda-ks.cfgと似たような感じだと思う。多分。

このファイルをuser-dataというファイル名で用意する。

ホスト名と初期アカウント

autoinstall:
  identity:
    hostname: ホスト名
    password: ハッシュ化されたパスワード
    realname: 本名
    username: ユーザー名

パスワードは手動インストールした時のものと同じであれば変更不要。
再設定する場合、パスワードはmkpasswdなどを使って生成可能。

[zaki@cloud-dev2 ~]$ mkpasswd -m sha512crypt
パスワード:   # ここにパスワード入力
$6$XUQvm3zdFpKdbqjw$nhFPnzTKbdq2IMV1eAyL9edKpNUcaKjn3RxcX/z3FUicMPnJ6iDto.VGELgrM8k/NY8IZIAEAyykpI42Sn7.p0

ストレージ設定

ここは正直ちょっとよくわからなかった…ので、ひたすらトライアンドエラー
BIOSの場合とUEFIの場合で微妙に異なるみたい。(/boot/boot/efiとか)

以下は、ディスクは1つで、BIOS使用・/bootに2GB・/に残り全てのパーティションを割り当てる設定(size: -1)。/はLVMを使用する。
/boot/以外にbios_grubフラグが設定されたブート領域(GRUBをインストールする領域)も必要。
(以下は1GB割り当ててるけど、多分そんなに要らない…気がする)

手動インストールしたときのuser-dataはその時のディスクサイズの数値が入っているので、そこを修正してしまえばほぼ良さそう。

構文としては、まず定義した内容のidを、次の定義でdevicevolumeで参照してるように見える。あとパーティションは順番にnumber: Nで1から連番振ってある。
インストールがうまくいったときの内容は以下の通り。

  storage:
    config:
    - ptable: gpt
      path: /dev/sda
      wipe: superblock
      preserve: false
      name: ''
      grub_device: true
      type: disk
      id: disk-sda
      # disk

    - device: disk-sda
      size: 1G
      flag: bios_grub
      number: 1
      preserve: false
      grub_device: false
      type: partition
      id: partition-0
      # ここまでgrub領域 #1

    - device: disk-sda
      size: 2G
      wipe: superblock
      flag: boot
      number: 2
      preserve: false
      grub_device: false
      type: partition
      id: partition-1
    - fstype: ext4
      volume: partition-1
      preserve: false
      type: format
      id: format-0
      ## ここまで/boot用 #2

    - device: disk-sda
      size: -1
      wipe: superblock
      flag: ''
      number: 3
      preserve: false
      grub_device: false
      type: partition
      id: partition-2
    - name: ubuntu-vg
      devices:
      - partition-2
      preserve: false
      type: lvm_volgroup
      id: lvm_volgroup-0
    - name: ubuntu-lv
      volgroup: lvm_volgroup-0
      size: -1
      wipe: superblock
      preserve: false
      type: lvm_partition
      id: lvm_partition-0
    - fstype: ext4
      volume: lvm_partition-0
      preserve: false
      type: format
      id: format-1
      ## ここまで/(ルート)用・LVM設定 #3

「残り全ての領域を割り当てる」ためのsize: -1指定は、最後のパーティションにしか設定できないので注意(インストール時に以下エラーで失敗する)

subiquity/Filesystem/apply_autoinstall_config: Partition(device=..., size=-1, wipe=..., ...) has negative size but is not final partition of Disk ...

meta-dataとvendor-dataファイル

user-dataと同じディレクトリに、meta-datavendor-dataファイルを作成しておく。
中身は不要で、空ファイルで良い。

meta-dataは無いとインストールに失敗する。
vendor-dataは無くても大丈夫だったが、リクエストがきて404になるログが何度も来て時間と処理が無駄なので、これも空ファイルを作っておく。

touch meta-data vendor-data

webで公開

ここは適当なwebサーバーで。なければコンテナなどで。
Dockerのhttpdコンテナであれば、user-data, meta-data, vendor-dataの3ファイルのあるディレクトリで以下実行。

docker run -d -p 8080:80 -v "$PWD":/usr/local/apache2/htdocs httpd

3つのファイルにリモートからHTTPアクセスできればOK
(3ファイルを同じディレクトリに配置する)

DVDブートと自動インストール

PXEブート(ネットワークブート)を使って起動パラメタを渡さない方法を使うので、DVDからブートするところは手動インストールと同じ。

Autoinstallの指定を入力するため、この画面でeを押下し、コマンドの編集モードを開く。

ここで以下のように入力。
(実はここも相当トライアルアンドエラーした結果…笑)

linux  /casper/vmlinuz  ---

となっている個所の---の手前に次の内容を入力する。

autoinstall ip=dhcp ds=nocloud-net\;s=http://<user-dataファイルを公開しているURL>

ちなみにURLは別にドキュメントルートである必要はなく、http://192.168.0.250/path/to/ubuntu/autoinstall/ とか、ファイルのある場所を指定する。(この場合、http://192.168.0.250/path/to/ubuntu/autoinstall/meta-datahttp://192.168.0.250/path/to/ubuntu/autoinstall/user-dataにアクセスできればOK)

これでCtrl-xを押下すれば指定のパラメタで起動し、user-dataの内容に従ってAutoinstallが始まる。

あとはしばらく放置しておけばインストールが完了し、コンソールはログイン画面に遷移する。手元の環境だと15minくらい。
初回のみコンソール画面でロケールSSH公開鍵設定が行われるのでそのログが表示される。

(Appendix) user-dataファイル例

今回インストールに使ったuser-dataファイル全文(一部マスク済み)は以下。
あとで気付いたけどロケールがJPじゃなかったw

#cloud-config
autoinstall:
  apt:
    disable_components: []
    geoip: true
    preserve_sources_list: false
    primary:
    - arches:
      - amd64
      - i386
      uri: http://jp.archive.ubuntu.com/ubuntu
    - arches:
      - default
      uri: http://ports.ubuntu.com/ubuntu-ports
  drivers:
    install: false
  identity:
    hostname: cloud-dev3
    password: ...
    realname: zaki
    username: zaki
  kernel:
    package: linux-generic
  keyboard:
    layout: jp
    toggle: null
    variant: ''
  locale: en_US.UTF-8
  network:
    ethernets:
      ens192:
        addresses:
        - 192.168.0.132/24
        gateway4: 192.168.0.1
        nameservers:
          addresses:
          - 192.168.0.19
          search: []
    version: 2
  ssh:
    allow-pw: true
    authorized-keys:
    - 'ssh-rsa ...'
    - 'ssh-rsa ...'
    install-server: true
  storage:
    config:
    - ptable: gpt
      path: /dev/sda
      wipe: superblock
      preserve: false
      name: ''
      grub_device: true
      type: disk
      id: disk-sda

    - device: disk-sda
      # .. 前述の通りなので以下省略 ..

  updates: security
  version: 1

参考サイト

PXE使う構成は2003年頃に自分で書いたページもあったり。。当時は外付けCD-ROMドライブを購入する余裕がなかった(笑)
[ネットワークインストール・マルチブート] Libretto L5 / Debian

[Ansible] ansible.builtin.gather_factsモジュールでfactsを収集するタスクを作成する

ansible.builtin.gather_factsモジュールを使うと、リモートホストのfacts変数を収集するタスクを定義できる。
収集する項目のサブセット指定も可能。
このモジュールはplayの定義でgather_factsfalseにしていても、タスク実行のタイミングでfactsを収集してくれる。

---
- hosts: all
  gather_facts: false

  tasks:
  - ansible.builtin.gather_facts:
      gather_subset:
        - network

  - ansible.builtin.debug:
      msg: "{{ ansible_facts }}"

使いどころとしては、facts変数を参照するrole単体を作成する場合に、「playでgather_factsを有効にしといてな」という使い方を要求することなく、role内でfactsの収集と参照を独立して実装できる。

注意点として、playでfacts変数を収集する設定(gather_factstrueの状態)の場合でも、ansible.builtin.gather_factsモジュールを使うタスクでのfacts変数収集も動作するため、その場合の処理時間は長くなる。

docs.ansible.com