AWXのPoC用環境をAlmaLinux9.0 + K3s上で構築してみた

# 序文
社外からVPN接続するための環境を自営のネットワーク機器で構築したが、インターネットからのアクセスなのでVPN接続のパスワードやアカウントロックアウトなんかのポリシーを少しきつめに設定したところ、アカウントのリセット依頼が頻繁に来るようになってしまった。
利用者のセルフサービスで何とかできないかと思って、前から何かで検証してみようと思っていたAWXでセルフサービス化できないかPoCをやってみることにした。

今回の問題解決だけならポリシーを調整すればよくて、AWXを使って云々するのは費用対効果的には割に合わないのだけど、前から何かでAWXを使ってみたかったので作ってみた。
本記事は、実験のためにPC内の閉じた環境でAWXを初期状態で触れるようにするまでの手順を書いたものである。

# 環境
手元PCの仮想化環境上で構築した。

* 仮想化ホスト環境
    * OSエディション：Windows 10 Pro
    * OSバージョン：21H2
    * OSビルド：19044.1826
    * プロセッサ：AMD Ryzen 5 Pro 4650U with Radeon Graphics 2.10GHz
    * 実装RAM：8GB
    * 仮想化：Hyper-V
* AWX実行環境(ゲストOS)
    * OS: AlmaLinux 9.0
    * プロセッサ数：1個
    * メモリ：3GB (Hyper-Vの動的メモリは無効)
    * その他仮想マシン設定をデフォルトから変えたところ
        * 世代：第2世代
        * セキュアブート：無効
        * ネットワークアダプター：Default Switch

この設定ではPCの外からはAWX実行環境にアクセスできないので、あくまでPC内での検証用途のものである。

# 構築手順
## OSインストール
インストールメディアを↓から入手する。今回は AlmaLinux-9.0-x86_64-minimal.iso をダウンロードする。
https://mirrors.almalinux.org/isos/x86_64/9.0.html

Hyper-Vマネージャで、仮想マシンを適当に新規作成する。このとき、少なくとも以下を設定する。
* 世代の指定：第2世代 (第1世代だとダメということはないけど、今回はこちらを使った)
* メモリの割り当て：3GB以上、動的メモリの使用のチェックを外す →何度か試して、最低3GBあれば何とか動きそうだった。実運用環境だともっと増やした方がよさそう。
* ネットワークの構成：Default Switchを選択 →仮想マシンからインターネットにWebアクセス(リポジトリアクセスのため)できれば他の設定でもよい
* セキュアブート：無効 →第2世代を選んだ場合にのみ存在する設定項目。仮想マシン新規作成ウィザードでは設定できないので、ウィザード完了後に仮想マシンの設定を開いて変更する。有効だと、OSインストールメディアからの起動に失敗する。

メモリが3GB以上空いた状態にしてから仮想マシンを起動し、インストーラーを実行する。最小構成でインストールするので、「インストールの概要」で以下の点のみ変更してインストールを開始する。
* インストール先：自動構成でよいので、選択してそのまま完了
* ネットワークとホスト名：Default SwitchでIPアドレスはDHCP配布されるので、ホスト名だけ任意に設定する。今回は"awx1"と設定する。
* ユーザーの作成：任意にユーザーを作成し「このユーザーを管理者にする」をONにする

インストールが終わってOS再起動したら、「ユーザーの作成」で作成したユーザーでログインできることを確認する。SSHでログインするときの接続先については、ホストOS内(Hyper-VのDefault Switch配下)からは`awx1.mshome.net`で接続可能(外部からは接続不可)。
ついでに`sudo visudo`でsudoersを開いて、後続作業でパスワード無しでsudoできるようにするために`#%wheel        ALL=(ALL)       NOPASSWD: ALL`の行のコメントを外して(先頭の"#"を削除して)変更を保存する。

## K3sインストール

最近のAWXはKubernetes環境へのインストーラーしか提供していないので、まずはKubernetes環境を構築する。AWX公式のドキュメントには[テスト用にMinikubeを使う手順が掲載されている](https://github.com/ansible/awx-operator#creating-a-minikube-cluster-for-testing)けど、社内システムとして本運用で使えそうなものにしたかったのでK3sを使うことにした。
インストールは、AlmaLinuxにSSHでログインし、以下のコマンド(出力は省略)だけでインストールできる。
```bash
$ sudo dnf -y install tar git
$ sudo systemctl disable firewalld --now
$ sudo update-crypto-policies --set DEFAULT:SHA1
$ curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644
$ sudo update-crypto-policies --set DEFAULT
```
`sudo update-crypto-policies --set DEFAULT:SHA1`については、一時的にSHA1を暗号化ポリシーに追加するためのものである。
RHEL8系のときはこれをしなくてもうまくいったけど、RHEL9系からSHA1がデフォルトの暗号化ポリシーから外されていて、デフォルトのままだとK3sのリポジトリのGPG鍵署名の検証に失敗するので実行している。
今後K3sリポジトリのGPG鍵署名がSHA2対応されれば不要になるかも。
インストール後に`sudo update-crypto-policies --set DEFAULT`で元に戻している。

firewalldは必要な通信許可だけ設定する方が望ましいが、今回は手抜きでdisableにした。

## AWXインストール

まず、事前準備としてkustomizeをインストールする。
最初のコマンドでカレントディレクトリにkustomizeの実行ファイルができるので、お好みでPATHの通っているところに置く。
```bash
$ curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash
$ sudo chown root.root kustomize
$ sudo mv kustomize /usr/local/bin/
```
次に、作業用のディレクトリを作成して、そこに移動する。
```bash
$ mkdir awx-operator
$ cd awx-operator
```
必要なマニフェストファイルを作成する。
```yaml:awx.yaml
---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx
spec:
  admin_user: admin
  admin_password_secret: awx-admin-password

  service_type: nodeport
  ingress_type: ingress
  ingress_tls_secret: awx-secret-tls
  hostname: awx1.mshome.net
```
```yaml:kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: awx
generatorOptions:
  disableNameSuffixHash: true
secretGenerator:
  - name: awx-secret-tls
    type: kubernetes.io/tls
    files:
      - tls.crt
      - tls.key
  - name: awx-admin-password
    type: Opaque
    literals:
      - password=write-your-password
resources:
  - github.com/ansible/awx-operator/config/default?ref=0.21.0
  #- awx.yaml
images:
  - name: quay.io/ansible/awx-operator
    newTag: 0.21.0
```
`write-your-password`は設定したいパスワードに任意に書き換える。
`#- awx.yaml`は後でコメントを外して使うためのもの。
`newTag`は https://github.com/ansible/awx-operator/releases を見て、存在するTagを何か選ぶ。今回0.21.0なのは、構築してみようとした時点でそれが最新版だったため。

AWXのWebサーバに設定する証明書(自己署名)を作成する。
```bash
$ openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -out tls.crt -keyout tls.key -subj "/CN=awx1.mshome.net/O=awx1.mshome.net" -addext "subjectAltName = DNS:awx1.mshome.net" 
```
awx-operatorのリソースを作成する。
```bash
$ kustomize build . | kubectl apply -f -
namespace/awx created
customresourcedefinition.apiextensions.k8s.io/awxbackups.awx.ansible.com created
customresourcedefinition.apiextensions.k8s.io/awxrestores.awx.ansible.com created
customresourcedefinition.apiextensions.k8s.io/awxs.awx.ansible.com created
serviceaccount/awx-operator-controller-manager created
role.rbac.authorization.k8s.io/awx-operator-awx-manager-role created
role.rbac.authorization.k8s.io/awx-operator-leader-election-role created
clusterrole.rbac.authorization.k8s.io/awx-operator-metrics-reader created
clusterrole.rbac.authorization.k8s.io/awx-operator-proxy-role created
rolebinding.rbac.authorization.k8s.io/awx-operator-awx-manager-rolebinding created
rolebinding.rbac.authorization.k8s.io/awx-operator-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/awx-operator-proxy-rolebinding created
configmap/awx-operator-awx-manager-config created
secret/awx-admin-password created
secret/awx-secret-tls created
service/awx-operator-controller-manager-metrics-service created
deployment.apps/awx-operator-controller-manager created
```
awx-operatorのポッドのREADY が 2/2 になるまで待つ
```bash
$ kubectl get pods -n awx
NAME                                               READY   STATUS    RESTARTS   AGE
awx-operator-controller-manager-59fbc545c4-rtl8v   2/2     Running   0          4m23s
```
次にawxのリソースを作成する。
kustomization.yamlのresourcesにawx.yamlを追加して(コメント状態で追加していたので、コメントを外して)からkustomize buildを実行する。
```bash
$ sed -i -e 's/#- awx.yaml/- awx.yaml/' kustomization.yaml  # ここではsedで編集しているけど、viなどのエディタで編集してもよい
$ kustomize build . | kubectl apply -f -
namespace/awx unchanged
customresourcedefinition.apiextensions.k8s.io/awxbackups.awx.ansible.com unchanged
customresourcedefinition.apiextensions.k8s.io/awxrestores.awx.ansible.com unchanged
customresourcedefinition.apiextensions.k8s.io/awxs.awx.ansible.com unchanged
serviceaccount/awx-operator-controller-manager unchanged
role.rbac.authorization.k8s.io/awx-operator-awx-manager-role configured
role.rbac.authorization.k8s.io/awx-operator-leader-election-role unchanged
clusterrole.rbac.authorization.k8s.io/awx-operator-metrics-reader unchanged
clusterrole.rbac.authorization.k8s.io/awx-operator-proxy-role unchanged
rolebinding.rbac.authorization.k8s.io/awx-operator-awx-manager-rolebinding unchanged
rolebinding.rbac.authorization.k8s.io/awx-operator-leader-election-rolebinding unchanged
clusterrolebinding.rbac.authorization.k8s.io/awx-operator-proxy-rolebinding unchanged
configmap/awx-operator-awx-manager-config unchanged
secret/awx-admin-password unchanged
secret/awx-secret-tls configured
service/awx-operator-controller-manager-metrics-service unchanged
deployment.apps/awx-operator-controller-manager unchanged
awx.awx.ansible.com/awx created
```
```kubectl get svc -n awx```を実行して、awx-serviceが出ていればサービスが起動できている。
```bash
$ kubectl get svc -n awx
NAME                                              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
awx-operator-controller-manager-metrics-service   ClusterIP   10.43.95.222   <none>        8443/TCP       12m
awx-postgres                                      ClusterIP   None           <none>        5432/TCP       106s
awx-service                                       NodePort    10.43.242.40   <none>        80:30080/TCP   52s
```
ただし、内部で初期化処理が走っているためか、AWXのGUIにアクセスできるまでにここから5分～10分くらいかかるので、しばらく待ってからWebブラウザでLinuxサーバーにHTTPS接続する。準備が終わっていないと404エラーや503エラーになるので、その時はもう少し待ってから接続すればよい。
証明書は自己署名証明書なのでブラウザの警告画面が出るが、無視して進むとログイン画面が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693903/5d69c781-3907-c5b8-7ec4-f326b63327a2.png)
ユーザー名はadminで、パスワードはawx.yamlに書いたものを入力すると管理者権限でログインできる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693903/ce7f9a30-1f44-5e62-4409-dce08bd088df.png)
以上で構築完了。

# 自動実行スクリプト
OSインストール後の今回手順は、以下のスクリプトで自動実行できる。
```bash:setup.sh
#/bin/bash

# 実行コマンドを表示する & エラー時はその場で終了する設定で動かすようにしてエラー時に状況が分かりやすいようにする
set -x
set -e

# 構築したい環境に応じて以下の変数は適宜編集する
awx_operator_tag=0.21.0
controller_fqdn=awx1.mshome.net
awx_admin_password=write-your-password

# k3s インストール
sudo dnf -y install tar git
sudo systemctl disable firewalld --now
sudo update-crypto-policies --set DEFAULT:SHA1
curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644
sudo update-crypto-policies --set DEFAULT

# kustomize インストール
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash
sudo chown root.root kustomize
sudo mv kustomize /usr/local/bin/

# AWX インストール
cd ~
mkdir awx-operator
cd awx-operator
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -out tls.crt -keyout tls.key -subj "/CN=${controller_fqdn}/O=${controller_fqdn}" -addext "subjectAltName = DNS:${controller_fqdn}" 

cat << EOS > awx.yaml
---
apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx
spec:
  admin_user: admin
  admin_password_secret: awx-admin-password

  service_type: nodeport
  ingress_type: ingress
  ingress_tls_secret: awx-secret-tls
  hostname: ${controller_fqdn}
EOS

cat << EOS > kustomization.yaml
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: awx
 
generatorOptions:
  disableNameSuffixHash: true
 
secretGenerator:
  - name: awx-secret-tls
    type: kubernetes.io/tls
    files:
      - tls.crt
      - tls.key
  - name: awx-admin-password
    type: Opaque
    literals:
      - password=${awx_admin_password}
 
resources:
  - github.com/ansible/awx-operator/config/default?ref=${awx_operator_tag}
  #- awx.yaml

images:
  - name: quay.io/ansible/awx-operator
    newTag: ${awx_operator_tag}
EOS

kustomize build . | kubectl apply -f -
# awx-operator-controller-manager が READY 2/2 になるまで待つ
while [ -z "$(kubectl get pods -n awx | grep "^awx-operator-controller-manager.*\s2/2")" ]; do sleep 30; done
kubectl get pods -n awx
 
sed -i -e 's/#- awx.yaml/- awx.yaml/' kustomization.yaml
kustomize build . | kubectl apply -f -
# kubectl get svc で awx-service が表示されるまで待つ
while [ -z "$(kubectl get svc -n awx | grep "awx-service")" ]; do sleep 30; done
kubectl get svc -n awx
```