OpenShiftでButaneを使ってMachine Configファイルを作成してchronyの設定をしてみた

# Butaneとは

Butaneは、MachineConfigのマニフェストファイルを作成する時に使えるツールです。

- [OCP 4.10 Docs / インストール / 22. インストール設定 / 22.1. ノードのカスタマイズ / 22.1.1. Butane でのマシン設定の作成](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.10/html-single/installing#installation-special-config-butane_installing-customizing)

(抜粋)
> Butane は、OCP が使用するコマンドラインユーティリティーで、マシン設定を作成するための便利で簡略化した構文を提供したり、マシン設定の追加検証を実行したりします。Butane が受け入れる Butane 設定ファイルの形式は、[OpenShift Butane config spec](https://coreos.github.io/butane/specs/) で定義されています。

例えば、RHCOSのchronyの設定などは、chronyの設定が入ったMachine Configを作成して、OCP導入時、もしくはOCP導入後に適用することができます。
このMachine Configのマニフェストファイルの作成に`butane`は使用することができます。
今回はchronyの設定を例として、実際に`butane`の使用例を記載しました。


# Butaneの入手

以下を見ると、ミラーサイトからダウンロードできるバイナリ版と、 [coreos/butane](https://coreos.github.io/butane/) のリンクに記載のあるコンテナ版の両方が使用できるようです。
今回は、OSを汚さないようにコンテナ版を使用してみることにしました。

- 導入(バイナリ版)
    - [Butane イメージのダウンロードページ](https://mirror.openshift.com/pub/openshift-v4/clients/butane/) 
    - OCP 4.10のドキュメントの以下の箇所に記載 
        - [OCP 4.10 Docs / インストール / 22. インストール設定 / 22.1. ノードのカスタマイズ / 22.1.1. Butane でのマシン設定の作成 / 22.1.1.2. Butane のインストール](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.10/html-single/installing#installation-special-config-butane-install_installing-customizing)

- 導入(コンテナ版)

    - コンテナ版のbutaneの使用方法は以下に記載がありました。
      - [OCP 4.10 Docs / インストール / 22. インストール設定 / 22.1. ノードのカスタマイズ / 22.1.1. Butane でのマシン設定の作成](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.10/html-single/installing#installation-special-config-butane_installing-customizing)
      - [coreos/butane](https://coreos.github.io/butane/)
      - [coreos/butane / Getting started](https://coreos.github.io/butane/getting-started/)
      
    - 検証時には、以下で確認するとコンテナ版もイメージのTagもバイナリのバージョンと同じで`0.14.0`が最新の模様でした。

    ```bash 
    # podman search quay.io/coreos/butane
    # skopeo inspect docker://quay.io/coreos/butane | jq -r .RepoTags
    ```


コンテナ版のbutaneを使用します。
最新版のTagのbutaneのイメージをプルします。
(今回はしていませんがproxy経由でpodman pullする場合は以下参照)
- [KB / Setting up HTTP Proxy variables for podman](https://access.redhat.com/solutions/3939131)

```bash
[root@bastion-01 ~]# podman images
REPOSITORY  TAG         IMAGE ID    CREATED     SIZE
[root@bastion-01 ~]#
[root@bastion-01 butane]# podman pull quay.io/coreos/butane:release
Trying to pull quay.io/coreos/butane:release...
Getting image source signatures
Copying blob a3ed95caeb02 done
Copying blob e598d93fd738 done
Writing manifest to image destination
Storing signatures
015f6d6140baace9dc2d3d9ea2c501e09946dab8f81d14edce2de2075649d195
[root@bastion-01 butane]#
[root@bastion-01 butane]# podman images
REPOSITORY             TAG         IMAGE ID      CREATED      SIZE
quay.io/coreos/butane  release     015f6d6140ba  8 weeks ago  6.61 MB
[root@bastion-01 butane]#
```

コンテナと意識しないでコマンドを使用できるように`butane`という名前のシェルを作って`/usr/local/bin` に置いています。

```bash
[root@bastion-01 butane]# vi butane
[root@bastion-01 butane]# cat butane
#!/bin/sh
exec podman run --rm --interactive         \
     --security-opt label=disable          \
     --volume "${PWD}":/pwd --workdir /pwd \
     quay.io/coreos/butane:release         \
     "${@}"
[root@bastion-01 butane]#
[root@bastion-01 butane]# ls -l butane
-rw-r--r--. 1 root root 202  3月 25 15:46 butane
[root@bastion-01 butane]#
[root@bastion-01 butane]# chmod +x butane
[root@bastion-01 butane]#
[root@bastion-01 butane]# ls -l butane
-rwxr-xr-x. 1 root root 202  3月 25 15:46 butane
[root@bastion-01 butane]#
[root@bastion-01 butane]# mv butane /usr/local/bin/
[root@bastion-01 butane]#
[root@bastion-01 butane]# ls -l /usr/local/bin/butane
-rwxr-xr-x. 1 root root 202  3月 25 15:46 /usr/local/bin/butane
[root@bastion-01 butane]#
```

バージョンは、v0.14.0で、ミラーサイトにおいてあったバイナリと同じバージョンでした。
```bash
[root@bastion-01 butane]# butane --version
Butane v0.14.0
[root@bastion-01 butane]#
```


# MachineConfigマニフェストファイルの作成(chronyの設定)


今回はignitionファイルにchronyに設定も入れるようにしてみました。
chrony タイムサービス (chronyd) で使用されるタイムサーバーおよび関連する設定は、chrony.conf ファイルのコンテンツを変更し、それらのコンテンツをMachineConfigのマニフェストファイルとしてノードに渡して設定する必要があります。

このMachine Configのマニフェストファイルによる設定はOCPクラスター導入後に適用しても良いのですが、時刻同期がうまくいかないとOCPクラスタのインストールに失敗することもあるので、このタイミングでchronyの設定を埋め込んだMachine Configのマニフェストファイルも作成しておいてインストール前に`ignition`ファイルに埋め込むことにしています。

- [OCP 4.10 Docs / インストール / 10. ベアメタルへのインストール / 10.4. ネットワークが制限された環境でのユーザーによってプロビジョニングされるベアメタルクラスターのインストール / 10.4.10. chrony タイムサービスの設定](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.10/html-single/installing#installation-special-config-chrony_installing-restricted-networks-bare-metal)


Machine Configのマニフェストファイルは`Butane`というツールで作成することができます。
今回は`chrony.conf`ファイルのコンテンツを含む"Butane設定"を作成します。たとえば、worker nodeで`chrony`を設定するには`99-worker-chrony.bu`ファイルを作成します。
今回はmaster、worker用に以下の2つのbuファイルを作成しました。

- 99-worker-chrony.bu
- 99-master-chrony.bu


buファイル、MachineConfigのマニフェストファイル作成は、バックアップの意味も込めて、一旦、OCPのインストール用ディレクトリではなく、別の場所で作成して保存しています。
作成
```bash
[root@bastion-01 work]# mkdir -p manifest/chrony
[root@bastion-01 work]#
[root@bastion-01 work]# vi manifest/chrony/99-worker-chrony.bu
[root@bastion-01 work]# vi manifest/chrony/99-master-chrony.bu
[root@bastion-01 work]#
[root@bastion-01 work]# ls -l manifest/chrony/
合計 8
-rw-r--r--. 1 root root 407  3月 26 18:57 99-master-chrony.bu
-rw-r--r--. 1 root root 407  3月 26 18:56 99-worker-chrony.bu
[root@bastion-01 work]#
```


作成したbuファイルの中身は以下になります。
chronyの設定ファイルの中身をそのまま平文で設定できるので、直感的にわかりやすいのが良いところかなと思います。

```yaml:99-worker-chrony.bu
variant: openshift
version: 4.10.0
metadata:
  name: 99-worker-chrony
  labels:
    machineconfiguration.openshift.io/role: worker
storage:
  files:
  - path: /etc/chrony.conf
    mode: 0644
    overwrite: true
    contents:
      inline: |
        pool bastion-01.cluster-01.example.local iburst
        driftfile /var/lib/chrony/drift
        makestep 1.0 3
        rtcsync
        logdir /var/log/chrony
```

```yaml:99-master-chrony.bu
variant: openshift
version: 4.10.0
metadata:
  name: 99-master-chrony
  labels:
    machineconfiguration.openshift.io/role: master
storage:
  files:
  - path: /etc/chrony.conf
    mode: 0644
    overwrite: true
    contents:
      inline: |
        pool bastion-01.cluster-01.example.local iburst
        driftfile /var/lib/chrony/drift
        makestep 1.0 3
        rtcsync
        logdir /var/log/chrony
```


Butaneを使用して、ノードに配信される設定を含む MachineConfigのマニフェストファイル (`99-worker-chrony.yaml`、`99-worker-chrony.yaml`) を生成します。

```bash
[root@bastion-01 work]# cd manifest/chrony/
[root@bastion-01 chrony]#
[root@bastion-01 chrony]# ls -l
合計 8
-rw-r--r--. 1 root root 407  3月 26 18:57 99-master-chrony.bu
-rw-r--r--. 1 root root 407  3月 26 18:56 99-worker-chrony.bu
[root@bastion-01 chrony]#
[root@bastion-01 chrony]# butane 99-worker-chrony.bu -o 99-worker-chrony.yaml
[root@bastion-01 chrony]# butane 99-master-chrony.bu -o 99-master-chrony.yaml
[root@bastion-01 chrony]#
[root@bastion-01 chrony]# ls -l
合計 16
-rw-r--r--. 1 root root 407  3月 26 18:57 99-master-chrony.bu
-rw-r--r--. 1 root root 565  3月 26 18:59 99-master-chrony.yaml
-rw-r--r--. 1 root root 407  3月 26 18:56 99-worker-chrony.bu
-rw-r--r--. 1 root root 565  3月 26 18:59 99-worker-chrony.yaml
[root@bastion-01 chrony]#
```

作成されたMacineConfigのマニフェストファイルの中身は以下になります。
```yaml:99-worker-chrony.yaml
# Generated by Butane; do not edit
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
  labels:
    machineconfiguration.openshift.io/role: worker
  name: 99-worker-chrony
spec:
  config:
    ignition:
      version: 3.2.0
    storage:
      files:
        - contents:
            source: data:,pool%20bastion-01.cluster-01.example.local%20iburst%0Adriftfile%20%2Fvar%2Flib%2Fchrony%2Fdrift%0Amakestep%201.0%203%0Artcsync%0Alogdir%20%2Fvar%2Flog%2Fchrony%0A
          mode: 420
          overwrite: true
          path: /etc/chrony.conf
```


```yaml:99-master-chrony.yaml
# Generated by Butane; do not edit
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
  labels:
    machineconfiguration.openshift.io/role: master
  name: 99-master-chrony
spec:
  config:
    ignition:
      version: 3.2.0
    storage:
      files:
        - contents:
            source: data:,pool%20bastion-01.cluster-01.example.local%20iburst%0Adriftfile%20%2Fvar%2Flib%2Fchrony%2Fdrift%0Amakestep%201.0%203%0Artcsync%0Alogdir%20%2Fvar%2Flog%2Fchrony%0A
          mode: 420
          overwrite: true
          path: /etc/chrony.conf
```

このマニフェストファイルは以下のいずれかの方法で適用できます。

- (1)OCPクラスターがまだ起動していない場合は、マニフェストファイルを生成した後に、MachineConfig オブジェクトファイルを `<installation_directory>/openshift`ディレクトリーに追加してから、chronyの設定も入ったignitionファイルを作成しクラスターの作成を続行します。
- (2)OCPクラスターがすでに実行中の場合は、普通にMachine Configのマニフェストファイルを`oc apply -f`で適用するだけです。
  ```
  $ oc apply -f ./99-worker-chrony.yaml
  ```


今回は、これからignitionを作成するので、(1)の方法をやってみます。
まずは作成したMachine Configのマニフェストファイルを`<installation_directory>/openshift/` にコピーします。

```bash
[root@bastion-01 chrony]# cd
[root@bastion-01 ~]#
[root@bastion-01 ~]# cd work/
[root@bastion-01 work]#
[root@bastion-01 work]# cp -p manifest/chrony/99-*.yaml openshift/openshift/
[root@bastion-01 work]#
[root@bastion-01 work]# ls -l openshift/openshift/
合計 32
-rw-r--r--. 1 root root  565  3月 26 18:59 99-master-chrony.yaml
-rw-r--r--. 1 root root  565  3月 26 18:59 99-worker-chrony.yaml
-rw-r-----. 1 root root  181  3月 26 18:37 99_kubeadmin-password-secret.yaml
-rw-r-----. 1 root root 2466  3月 26 18:37 99_openshift-cluster-api_master-user-data-secret.yaml
-rw-r-----. 1 root root 2466  3月 26 18:37 99_openshift-cluster-api_worker-user-data-secret.yaml
-rw-r-----. 1 root root  543  3月 26 18:37 99_openshift-machineconfig_99-master-ssh.yaml
-rw-r-----. 1 root root  543  3月 26 18:37 99_openshift-machineconfig_99-worker-ssh.yaml
-rw-r-----. 1 root root  174  3月 26 18:37 openshift-install-manifests.yaml
[root@bastion-01 work]#
```

(ignitionを作成するとマニフェストファイルは消えてしまうので必要に応じて適宜バックアップをとっておきます。)


# ignitionファイルの作成


`openshift-install create ignition-configs --dir <installation_directory>`コマンドでignitionファイルを作成します。

```bash
[root@bastion-01 work]# ls -la openshift/
合計 148
drwxr-xr-x. 4 root root    107  3月 26 18:37 .
drwxr-xr-x. 4 root root     37  3月 26 18:33 ..
-rw-r--r--. 1 root root  18682  3月 26 18:37 .openshift_install.log
-rw-r-----. 1 root root 120005  3月 26 18:37 .openshift_install_state.json
drwxr-x---. 2 root root   4096  3月 26 18:37 manifests
drwxr-x---. 2 root root   4096  3月 26 18:37 openshift
[root@bastion-01 work]#
[root@bastion-01 work]# openshift-install create ignition-configs --dir openshift
INFO Consuming Master Machines from target directory
INFO Consuming Common Manifests from target directory
INFO Consuming Worker Machines from target directory
INFO Consuming OpenShift Install (Manifests) from target directory
INFO Consuming Openshift Manifests from target directory
INFO Ignition-Configs created in: openshift and openshift/auth
[root@bastion-01 work]#
```

確認
```bash
[root@bastion-01 work]# tree openshift/
openshift/
├── auth
│   ├── kubeadmin-password
│   └── kubeconfig
├── bootstrap.ign
├── master.ign
├── metadata.json
└── worker.ign

1 directory, 6 files
[root@bastion-01 work]#
[root@bastion-01 work]# tree -a openshift/
openshift/
├── .openshift_install.log
├── .openshift_install_state.json
├── auth
│   ├── kubeadmin-password
│   └── kubeconfig
├── bootstrap.ign
├── master.ign
├── metadata.json
└── worker.ign

1 directory, 8 files
[root@bastion-01 work]#
```

このようにして、chronyの設定も含んだignitionファイルが作成されました。
(bootstrap.ignの中に99-master-chrony.yamlと99-worker-chrony.yamlの設定の記載がありました。)

あとは、これらのIgnitionファイルを使って、普通にOCPのインストールを実施すれば、RHCOSにchronyの設定が適用されています。


(OCPインストール手順自体はこちらもご参照ください。)

- [Qiita / OpenShift 4.10 ベアメタルUPI (iPXE) インストール](https://qiita.com/t_sweetfish/items/81268b29a3a7ad2f3f09)



# OCPインストール後の確認


OCPインストール後に、ちゃんとchronyのMachine Configが適用され、RHCOSにchronyの設定が反映していることがわかります。



```bash
[root@bastion-01 ~]# oc get mc
NAME                                               GENERATEDBYCONTROLLER                      IGNITIONVERSION   AGE
00-master                                          e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
00-worker                                          e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
01-master-container-runtime                        e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
01-master-kubelet                                  e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
01-worker-container-runtime                        e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
01-worker-kubelet                                  e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
99-master-chrony                                                                              3.2.0             64d
99-master-generated-registries                     e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
99-master-ssh                                                                                 3.2.0             64d
99-worker-chrony                                                                              3.2.0             64d
99-worker-generated-registries                     e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
99-worker-ssh                                                                                 3.2.0             64d
rendered-infra-8062fb3f341ce6e926a1678cc33db7f3    e6ba00b885558712d660a3704c071490d999de6f   3.2.0             24d
rendered-master-954e86f747cb1bc813dc88a53adc068c   e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
rendered-worker-8062fb3f341ce6e926a1678cc33db7f3   e6ba00b885558712d660a3704c071490d999de6f   3.2.0             64d
[root@bastion-01 ~]#
```


master nodeでは、buファイルに指定したパス、権限、ファイル内容で/etc/chrony.confが作成されています。
(また、chronyc sourcesでは、api.cluster-01.example.localと同期しているように表示されていますが、chrony.confに定義したbastion-01.cluster-01.example.localと同じサーバーなので、名前解決の問題で表示だけの問題かなと思います。)

```bash
[root@bastion-01 ~]# ssh core@master-01
Red Hat Enterprise Linux CoreOS 410.84.202203221702-0
  Part of OpenShift 4.10, RHCOS is a Kubernetes native operating system
  managed by the Machine Config Operator (`clusteroperator/machine-config`).

WARNING: Direct SSH access to machines is not recommended; instead,
make configuration changes via `machineconfig` objects:
  https://docs.openshift.com/container-platform/4.10/architecture/architecture-rhcos.html

---
Last login: Thu May 26 08:09:26 2022 from 172.16.100.11
[core@master-01 ~]$
[core@master-01 ~]$ ls -l /etc/chrony.conf
-rw-r--r--. 1 root root 126 Mar 30 11:28 /etc/chrony.conf
[core@master-01 ~]$
[core@master-01 ~]$ cat /etc/chrony.conf
pool bastion-01.cluster-01.example.local iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
logdir /var/log/chrony
[core@master-01 ~]$
[core@master-01 ~]$ chronyc sources
210 Number of sources = 1
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* api.cluster-01.example.l>     3   8   377   101  -6990ns[-7352ns] +/-   44ms
[core@master-01 ~]$
[core@master-01 ~]$ exit
logout
Connection to master-01 closed.
[root@bastion-01 ~]#
```

worker nodeも同様です。
```bash
[root@bastion-01 ~]# ssh core@worker-01
Red Hat Enterprise Linux CoreOS 410.84.202203221702-0
  Part of OpenShift 4.10, RHCOS is a Kubernetes native operating system
  managed by the Machine Config Operator (`clusteroperator/machine-config`).

WARNING: Direct SSH access to machines is not recommended; instead,
make configuration changes via `machineconfig` objects:
  https://docs.openshift.com/container-platform/4.10/architecture/architecture-rhcos.html

---
Last login: Sun Apr 10 05:11:46 2022 from 172.16.100.11
[core@worker-01 ~]$
[core@worker-01 ~]$ ls -l /etc/chrony.conf
-rw-r--r--. 1 root root 126 Mar 30 11:33 /etc/chrony.conf
[core@worker-01 ~]$
[core@worker-01 ~]$ cat /etc/chrony.conf
pool bastion-01.cluster-01.example.local iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
logdir /var/log/chrony
[core@worker-01 ~]$
[core@worker-01 ~]$ chronyc sources
210 Number of sources = 1
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* api.cluster-01.example.l>     3   9   377   273    +41us[ +121us] +/-   44ms
[core@worker-01 ~]$
[core@worker-01 ~]$ exit
logout
Connection to worker-01 closed.
[root@bastion-01 ~]#
```
以上


# 補足(filetranspiler)
> - OCPの昔のバージョンでのchronyの設定方法
> OCP 4.3などの頃は、インストール時にfiletranspilerというツールを使用して、chronyの設定や複雑なネットワーク構成などをignitionファイルに埋め込むことできたのですが、OCP 4.10のドキュメントにはどこにもfiletranspilerの記載がなく、butaneでMachine Configを作成する方法 が書いていました。
> また、その頃も、OCPインストール後にchronyを設定する際にMachine Configで適用する方法もあったのですが、当時はchronyの設定ファイルをbase64でエンコードしMachine Configのマニフェストファイルに手動で記載するような手順でした。今回のbutaneを使用すると、設定ファイルの内容をわざわざbas64エンコードとかしなくても勝手にやってくれるので、Machine Configのマニフェストファイルを作成する場合は、butaneの方が少し楽になるかなと思います。
> (また当時はOCPのドキュメント(カーネルモジュールを読み込む文脈ですが)filetranspilerツールの記載もあったためサポートもあったと思うのですが、OCP 4.10のドキュメントではfiletranspilerの記述はなくなっているので、やはりbutaneを使用してMachine Configを作成して適用する方法の方が良いような気がしています。)
> (参考資料)
> - filetranspilerで複数NIC、bonding、静的IPアドレス、静的ルート、chronyなどの設定をIgnitionに取り込んでデプロイする方法
>   - [Red Hat OpenShift Blog / Advanced Network Customizations for OpenShift Install](https://www.openshift.com/blog/advanced-network-customizations-for-openshift-install)
>   - [ashcrow / filetranspiler](https://github.com/ashcrow/filetranspiler)