zaki work log

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

[VS Code / Windows] ターミナルのデフォルトシェルをGit BashやCygwinのシェルにする

WindowsにインストールしたVS CodeのターミナルはデフォルトでPowerShellだけど、設定でGit BashCygwinのシェルに変更できる。

設定方法

Git Bash

Git for WindowsをインストールするとついてくるGit Bashは、VS Codeが自動で認識するので設定画面で設定できる。
「Features > Terminal > Integrated › Default Profile: Windows」を探し、ドロップダウンメニューから「Git Bash」を選択する。

settings.jsonに直接記述する場合は以下の通り。

    "terminal.integrated.defaultProfile.windows": "Git Bash",

Cygwin bash

Cygwinbashをデフォルトのシェルにする場合は、デフォルトシェルの設定に加えてCygwinbashVS Codeに認識させる(登録する)必要がある。設定は以下の通り。
設定の雛形は「Features > Terminal > Integrated > Profiles: Windows」の「Edit in settings.json」をクリックすればsettings.jsonに挿入される。

    "terminal.integrated.defaultProfile.windows": "Cygwin",
    "terminal.integrated.profiles.windows": {
        "Cygwin": {
            "path": "C:\\cygwin64\\bin\\bash.exe",
            "args": ["--login"]
        },
    }

terminal.integrated.profiles.windows"設定名": { "path": "シェルの実行ファイルのパス", "args": "実行ファイルの引数"}を指定する。
デフォルトで使えるPowerShellとGit Bashの設定は不要(無くても動作する)

--loginは無くても動作の違いが実はわかっていないんだけど、指定しなくても良いのかな?(「明示的にログインする」場合に指定するらしいけど、VS Codeからの「起動」は当てはまらない、とか…?)

Cygwin zsh

Cygwin bashの場合と基本的に同じ。前述のCygwin bashzshも追加で設定する場合は以下の通り。

    "terminal.integrated.defaultProfile.windows": "Cygwin zsh",
    "terminal.integrated.profiles.windows": {
        "Cygwin zsh": {
            "path": "C:\\cygwin64\\bin\\zsh.exe",
            "args": ["--login"]
        },
        "Cygwin": {
            "path": "C:\\cygwin64\\bin\\bash.exe",
            "args": ["--login"]
        },
    }

以前はbash.exeをまず呼び出して、chereパッケージに含まれるxhereを使って["/bin/xhere", "/bin/zsh"]と引数に指定したりしてたけど不要になってる。

補足

ググるterminal.integrated.shell.windowsを設定する例がヒットするが、現在この設定はdeprecatedになっている。設定しているとVS CodeのUIの設定画面だと警告が表示される。

This is deprecated, the new recommended way to configure your default shell is by creating a terminal profile in Terminal › Integrated › Profiles: Windows and setting its profile name as the default in Terminal › Integrated › Default Profile: Windows. This will currently take priority over the new profiles settings but that will change in the future.

これはCygwinのシェルをセットするときに使う。 terminal.integrated.shellArgs.windowsについても同様。

[K3s / Kubernetes] KUBECONFIGを通常ユーザーでも読めるように起動オプションを指定する

TL;DR

結論を先に書くとK3sデプロイ時に、以下のように--write-kubeconfig-modeオプションでモードを付与すればOK

curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644

K3sのKUBECONFIGファイル

K3sを特にオプションなしでデプロイした場合、KUBECONFIGファイルは/etc/rancher/k3s/k3s.yamlが使われる。
このファイルは以下の通り、デフォルトではrootのみが読み込み可能。

$ ls -l /etc/rancher/k3s/k3s.yaml
-rw------- 1 root root 2957 Jan  9 14:14 /etc/rancher/k3s/k3s.yaml

よって通常ユーザーだとK3sをデプロイしたホストでkubectlを実行しても以下の通り権限エラーとなる。

$ kubectl get node
WARN[0000] Unable to read /etc/rancher/k3s/k3s.yaml, please start server with --write-kubeconfig-mode to modify kube config permissions 
error: error loading config file "/etc/rancher/k3s/k3s.yaml": open /etc/rancher/k3s/k3s.yaml: permission denied

エラーメッセージに表示されているように、--write-kubeconfig-modeオプションを使うのが正解だけど、読み取り権限ということでchmodパーミッションを変更してみると…

$ sudo chmod 644 /etc/rancher/k3s/k3s.yaml
$ kubectl get node
NAME               STATUS   ROLES                  AGE     VERSION
oci-ap-a1-ubuntu   Ready    control-plane,master   3d16h   v1.25.4+k3s1

この通り使用可能になる。。。
ように見えるけど、実はこのファイル、K3sのサービス起動時に状態が復元(再作成)されるため、リスタートしたりOSリブートしたりするとchmodで変更した内容が無くなり、通常ユーザーは再び読み取り不可になる。

$ ls -l /etc/rancher/k3s/k3s.yaml
-rw-r--r-- 1 root root 2957 Jan  9 14:14 /etc/rancher/k3s/k3s.yaml
$ sudo systemctl restart k3s.service
$ ls -l /etc/rancher/k3s/k3s.yaml
-rw------- 1 root root 2957 Jan 12 00:04 /etc/rancher/k3s/k3s.yaml

ということで、(通常ユーザーで運用する場合は)大人しく起動オプションを使って権限を指定しましょう。

起動オプションと環境変数の指定方法

docs.k3s.io

例としてドキュメントに載っている「flannelを使わずトークンでserverを登録する」は以下。(全て同じ処理)

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--flannel-backend none --token 12345" sh -s -
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --flannel-backend none" K3S_TOKEN=12345 sh -s -
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server" sh -s - --flannel-backend none
curl -sfL https://get.k3s.io | K3S_TOKEN=12345 sh -s - server --flannel-backend none
curl -sfL https://get.k3s.io | sh -s - --flannel-backend none --token 12345

起動オプションと環境変数一覧

server(コントロールプレーン)とagent(ワーカー)でそれぞれオプションがある。
本記事で紹介したKUBECONFIGのモードはserverのオプション。

[Kubernetes] 軽量ディストリビューションk3sのコンテナ版の紹介

本記事は「エーピーコミュニケーションズ Advent Calendar 2022」の21日目のエントリです。

軽量Kubernetesディストリビューションの一つにk3sというものがあります。
以下のコマンドで簡単にクラスタをデプロイでき、少ないリソースで動作し、プラットフォームもARMにも対応しているので幅広く使用できます。(Raspberry Piとか、Oracle Cloud無料枠のARMコンピュートインスタンスとか)

curl -sfL https://get.k3s.io | sh - 

k3s.io

実はこのk3sはコンテナ版もあって、Linux OSに直接インストールするのでなく、Dockerコンテナとしてノードを動作させることもできます。Docker上でKubernetesをデプロイできるkindと同じで、Kubernetes in Dockerの構成。

コンテナ版k3sのデプロイには、Docker Composeを使う方法と、CLIツールを使う方法があり、本記事ではそれぞれ紹介します。
CLIの方はkindと使い勝手的にはかなり似ています。k3sは「停止」が用意されているので「使いたいときだけ動かす」がやりやすいかも。(kindだとdocker stopを直接実行するしかない…よね)

CLIを使った構築

k3dインストール

k3dコマンドのインストール

$ curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
Preparing to install k3d into /usr/local/bin
k3d installed into /usr/local/bin/k3d
Run 'k3d --help' to see what you can do with it.

他にもプラットフォームにあわせてHomebrewやChocolateyを使用可能。
他のバージョンを使いたいなどは、インストールドキュメント参照

クラスタのデプロイ

最も単純な1ノード(コントロールプレーン・ワーカーのオールインワンシングルノード構成)の場合はオプション指定も特になくcreateのみでOK
(※この場合コントロールプレーンノードのスケールはあとからできない。後述)

$ k3d cluster create
INFO[0000] Prep: Network                                
INFO[0000] Created network 'k3d-k3s-default'            
INFO[0000] Created image volume k3d-k3s-default-images  
INFO[0000] Starting new tools node...                   
INFO[0001] Creating node 'k3d-k3s-default-server-0'     
INFO[0001] Pulling image 'ghcr.io/k3d-io/k3d-tools:5.4.6' 
INFO[0003] Starting Node 'k3d-k3s-default-tools'        
INFO[0003] Pulling image 'docker.io/rancher/k3s:v1.24.4-k3s1' 
INFO[0016] Creating LoadBalancer 'k3d-k3s-default-serverlb' 
INFO[0017] Pulling image 'ghcr.io/k3d-io/k3d-proxy:5.4.6' 
INFO[0020] Using the k3d-tools node to gather environment information 
INFO[0021] HostIP: using network gateway 172.18.0.1 address 
INFO[0021] Starting cluster 'k3s-default'               
INFO[0021] Starting servers...                          
INFO[0021] Starting Node 'k3d-k3s-default-server-0'     
INFO[0027] All agents already running.                  
INFO[0027] Starting helpers...                          
INFO[0027] Starting Node 'k3d-k3s-default-serverlb'     
INFO[0033] Injecting records for hostAliases (incl. host.k3d.internal) and for 2 network members into CoreDNS configmap... 
INFO[0036] Cluster 'k3s-default' created successfully!  
INFO[0036] You can now use it like this:                
kubectl cluster-info

マルチノードの場合はオプションを指定します。

$ k3d cluster create --servers 3 --agents 2

起動するとホストOSの$HOME/.kube/configが生成・更新されるので、kubectlはすぐ使用可能。ノード状態は以下の通り。

zaki@dev-server:~$ kubectl get node
NAME                       STATUS   ROLES                       AGE   VERSION
k3d-k3s-default-agent-0    Ready    <none>                      26s   v1.24.4+k3s1
k3d-k3s-default-agent-1    Ready    <none>                      26s   v1.24.4+k3s1
k3d-k3s-default-server-0   Ready    control-plane,etcd,master   60s   v1.24.4+k3s1
k3d-k3s-default-server-1   Ready    control-plane,etcd,master   46s   v1.24.4+k3s1
k3d-k3s-default-server-2   Ready    control-plane,etcd,master   33s   v1.24.4+k3s1

クラスタの状態

K3dとしてのクラスタ一覧

$ k3d cluster list
NAME          SERVERS   AGENTS   LOADBALANCER
k3s-default   1/1       0/0      true

コンテナの状態。
コントロールプレーンノード以外にLBやAPIサーバーとして動作するコンテナが1つ起動しています。

$ docker ps
CONTAINER ID   IMAGE                            COMMAND                  CREATED              STATUS              PORTS                             NAMES
cb7ba9c9a6ed   ghcr.io/k3d-io/k3d-proxy:5.4.6   "/bin/sh -c nginx-pr…"   About a minute ago   Up 54 seconds       80/tcp, 0.0.0.0:43515->6443/tcp   k3d-k3s-default-serverlb
75a6838bc838   rancher/k3s:v1.24.4-k3s1         "/bin/k3d-entrypoint…"   About a minute ago   Up About a minute                                     k3d-k3s-default-server-0

K8sとしてのクラスタ情報

$ kubectl cluster-info
Kubernetes control plane is running at https://0.0.0.0:43515
CoreDNS is running at https://0.0.0.0:43515/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Metrics-server is running at https://0.0.0.0:43515/api/v1/namespaces/kube-system/services/https:metrics-server:https/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
zaki@dev-server:~$ kubectl get node
NAME                       STATUS   ROLES                  AGE     VERSION
k3d-k3s-default-server-0   Ready    control-plane,master   3m20s   v1.24.4+k3s1
zaki@dev-server:~$ kubectl get pod -A
NAMESPACE     NAME                                      READY   STATUS      RESTARTS   AGE
kube-system   local-path-provisioner-7b7dc8d6f5-tt94v   1/1     Running     0          3m8s
kube-system   coredns-b96499967-q5kbw                   1/1     Running     0          3m8s
kube-system   helm-install-traefik-crd-5w7mv            0/1     Completed   0          3m8s
kube-system   helm-install-traefik-xqjkn                0/1     Completed   1          3m8s
kube-system   svclb-traefik-50cc15de-xt892              2/2     Running     0          2m40s
kube-system   metrics-server-668d979685-j4j62           1/1     Running     0          3m8s
kube-system   traefik-7cd4fcff68-78nhc                  1/1     Running     0          2m41s

ノードをあとから追加する

ワーカー(agent)

ワーカーノードを1台追加するには以下のコマンド。

$ k3d node create agent
zaki@dev-server:~$ k3d node list
NAME                       ROLE           CLUSTER       STATUS
k3d-agent-0                agent          k3s-default   running
k3d-k3s-default-server-0   server         k3s-default   running
k3d-k3s-default-serverlb   loadbalancer   k3s-default   running
zaki@dev-server:~$ kubectl get node
NAME                       STATUS   ROLES                  AGE   VERSION
k3d-k3s-default-server-0   Ready    control-plane,master   12d   v1.24.4+k3s1
k3d-agent-0                Ready    <none>                 30s   v1.24.4+k3s1

コントロールプレーン(server)

コントロールプレーンを追加するには--roleオプションを指定する。(デフォルトagentなのでワーカーが追加される)

$ k3d node create master --role server

ただし、「クラスタ作成時に1サーバーノードで作成した場合(--servers nを指定しなかった場合)」は以下の通りエラーとなります。

zaki@dev-server:~$ k3d node create master --role server
INFO[0000] Adding 1 node(s) to the runtime local cluster 'k3s-default'... 
INFO[0000] Using the k3d-tools node to gather environment information 
INFO[0000] Starting new tools node...                   
INFO[0000] Starting Node 'k3d-k3s-default-tools'        
INFO[0000] HostIP: using network gateway 172.22.0.1 address 
INFO[0000] Starting Node 'k3d-master-0'                 
WARN[0001] warning: encountered fatal log from node k3d-master-0 (retrying 0/10): �time="2022-12-18T03:49:11Z" level=fatal msg="starting kubernetes: preparing server: https://k3d-k3s-default-server-0:6443/v1-k3s/server-bootstrap: 400 Bad Request" 
WARN[0001] warning: encountered fatal log from node k3d-master-0 (retrying 1/10): �time="2022-12-18T03:49:12Z" level=fatal msg="starting kubernetes: preparing server: https://k3d-k3s-default-server-0:6443/v1-k3s/server-bootstrap: 400 Bad Request" 

[snip]

WARN[0006] warning: encountered fatal log from node k3d-master-0 (retrying 9/10): �time="2022-12-18T03:49:16Z" level=fatal msg="starting kubernetes: preparing server: https://k3d-k3s-default-server-0:6443/v1-k3s/server-bootstrap: 400 Bad Request" 
FATA[0006] failed to add 1 node(s) to the runtime local cluster 'k3s-default': failed to add one or more nodes: failed to run node 'k3d-master-0': failed to start node 'k3d-master-0': Node k3d-master-0 failed to get ready: error waiting for log line `k3s is up and running` from node 'k3d-master-0': stopped returning log lines: node k3d-master-0 is running=true in status=restarting

🔥 There’s a trap!

If your cluster was initially created with only a single server node, then this will fail. That’s because the initial server node was not started with the --cluster-init flag and thus is not using the etcd backend.

Adding server nodes to a running cluster

構成のコード化

kindと同様に、クラスタ作成時のオプションを設定ファイルに記述することもできます。
例えばコントロールプレーン3台、ワーカー2台のクラスタであれば以下の通り。

apiVersion: k3d.io/v1alpha4
kind: Simple
metadata:
  name: mycluster
servers: 3
agents: 2

このファイルをk3d-config.yamlとして用意し、クラスタ作成時に以下コマンドを実行すれば指定の構成でデプロイされます。

$ k3d cluster create -c k3d-config.yaml 
$ k3d cluster list
NAME        SERVERS   AGENTS   LOADBALANCER
mycluster   3/3       2/2      true

他にもオプションで指定できる様々な値をYAMLで指定できます。
詳細はドキュメント参照。

k3d.io

Docker Composeを使った構築

もう一つの構築方法がDocker Composeを使った方法。

前提としてDockerとDocker Composeがインストール済みであること。
(現在はdocker-composeコマンドでなくDockerのプラグインとしてインストールする。手順通りにインストールすればdocker-compose-pluginパッケージに含まれるので使用可能になっているはず)

docs.docker.com

Composeを使ったコンテナ版k3sのデプロイ方法はドキュメントはあまり詳しく載っておらず、「Running K3s in Docker」や、「日本語版K3sマニュアル(PDF版)」の49ページ「8.9 K3d(Docker で動く k3s)を docker-compose で動かす」という項で確認できます。(が、情報量が少ない)

Composeファイル

デプロイに使用するComposeファイルはk3sリポジトリのルートにあります。

github.com

内容を見ればわかりますが、server(コントロールプレーン)と、agent(ワーカー)の2つのノードが起動する設定になっています。
また、server上でAPIサーバーやIngress Controllerが動作するためのポートのpublish設定なども確認できます。

クラスタのデプロイ

Docker Composeの通常の操作で、docker compose upを実行してデプロイします。
ただし準備として、コントロールプレーンとワーカーが互いに通信するためのトークンを環境変数で設定しておく必要があります。

[zaki@cloud-dev2 k3s-compose]$ export K3S_TOKEN=${RANDOM}${RANDOM}${RANDOM} 
[zaki@cloud-dev2 k3s-compose]$ 
[zaki@cloud-dev2 k3s-compose]$ docker compose up -d
[+] Running 4/4
 ⠿ Network k3s-compose_default      Created                                                               0.2s
 ⠿ Volume "k3s-compose_k3s-server"  Created                                                               0.0s
 ⠿ Container k3s-compose-server-1   Started                                                               1.0s
 ⠿ Container k3s-compose-agent-1    Started                                                               1.0s
[zaki@cloud-dev2 k3s-compose]$ 
[zaki@cloud-dev2 k3s-compose]$ docker compose ps
NAME                   COMMAND             SERVICE             STATUS              PORTS
k3s-compose-agent-1    "/bin/k3s agent"    agent               running             
k3s-compose-server-1   "/bin/k3s server"   server              running             0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:6443->6443/tcp, :::6443->6443/tcp

デフォルトの動作では、kubeconfig.yamlはカレントディレクトリに生成されるので、--kubeconfigで指定すれば(またはKUBECONFIG環境変数指定)kubectlが使用可能。

[zaki@cloud-dev2 k3s-compose]$ kubectl get node --kubeconfig ./kubeconfig.yaml 
NAME           STATUS   ROLES                  AGE   VERSION
11d52309842b   Ready    control-plane,master   48s   v1.24.3+k3s1
36e609703847   Ready    <none>                 44s   v1.24.3+k3s1

K3S_TOKENの値はComposeファイル内に指定の方法が記載されています
頻繁に作成と削除を繰り返す場合は、$HOME/.bashrc等で指定しておくのもアリ。ランダムである必要もないので、適当な固定値でもOK

マルチノードの制限

マルチノードについては、Compose版はいろいろ試した限り制限がありそう。

起動時のノード指定

ワーカーノードのみマルチノードにする場合はdocker compose up--scaleオプションで対応可能。
以下のコマンドであれば3ワーカーノードのクラスタをデプロイできます。

$ docker compose up -d --scale agent=3

ただし、コントロールプレーンはポートのpublish設定があるため、単純に--scaleしても起動しません。APIサーバー用のコンテナが起動してコントロールプレーンへのLBとして動作するCLI版と異なり、おそらくコンテナ自身がAPIサーバーとして動作する設計になっているため、スケールできないと思われます。
(たぶんCLI版と同じ構成になるようにComposeファイルを作成すれば動く…かも。未検証)

起動後のノード追加

これはできるかと思ったけど今のところうまく行かなかった。
running中の状態でdocker compose up -d --scale agent=Xすると、動作的には新しいワーカーノードは追加されるが、元からrunningだったノードがNot Readyになってしまった。
これも現状未解決。

CLI版と同じ動きになるようにdocker runでネットワークやボリュームを手動で指定しつつ既存クラスタへ追加することは、技術的には可能と思われるが、こちらも未検証。

まとめ

軽量Kubernetesであるk3sのコンテナ版を2パターン紹介しました。コンテナの特徴である作って壊すが簡単にできるので、検証用途などには特に使い勝手は良いと思います。それぞれ特徴があるので簡単に私見でまとめ。

資料

K3dだったりk3dだったり、公式の表記ゆれが多くてどっちがただしいんだ。。

[Ansible] 環境変数の利用についておさらい

本記事は「Ansible Advent Calendar 2022」の19日目のエントリとなります。
Ansibleで環境変数を参照したりタスク実行時にセットしたりする方法についておさらい。

おさらいして整理してると気付けたけど、「ローカルか、リモートか」でなく、「Ansible本体の実行環境か、マネージドノードのタスクの実行環境か」という区切りで考えるのがポイント。(環境変数に限らず、だけど)

環境変数の参照

コントロールノード(ローカル)の環境変数を参照

大前提として「ルックアップ(lookup)プラグインはコントロールノードで処理される」という仕様があります。もう少し細かくいうと、(対象がローカルの場合を含むマネージドノード上の)タスクを実行するタイミングでなく、(CLIであれば)ansible-playbookを実行するノードで処理されます。
このルックアッププラグインの1つで環境変数を参照できるenvルックアッププラグインを使えばタスク実行時などにローカルの環境変数を参照できます。

環境変数zzzをprintするサンプルは以下。

---
- hosts: server

  tasks:
  - debug:
      msg: "{{ lookup('env', 'zzz') }}"

環境変数zzzhelloをセットした実行例が以下。
(実行ノードのawx.jp-z.jpはリモートです)

$ export zzz=hello
$ ansible-playbook -i inventory.ini print-env-sample.yml 

PLAY [server] ***************************************************************

TASK [debug] ****************************************************************
ok: [awx.jp-z.jp] => {
    "msg": "hello"
}
ok: [localhost] => {
    "msg": "hello"
}

ルックアッププラグイン自体の説明はこちら

docs.ansible.com

envルックアッププラグイン

docs.ansible.com

マネージドノード(リモート)の環境変数を参照

ノードのfacts変数のenvを参照します。具体的にはansible_facts.envに取得できた環境変数が全てセットされています。(あるいは以前からの書式でansible_env)
facts変数で環境変数を収集するには、gather_factsを使用します。(デフォルト有効)
gather_subsetで収集する範囲を限定する場合でも、環境変数min指定でもOKの模様。(明示的にenvも指定可能)

環境変数USERをprintするサンプルは以下。

---
- hosts: server
  gather_facts: true
  gather_subset: min

  tasks:
  - debug:
      msg: "{{ ansible_facts.env.USER }}"

ansible_connectionでコネクションプラグインlocalを使ってローカル実行のUSERzaki、リモートはubuntuという環境での実行例。
以下はデフォルトの状態で実行。ローカル(ansible-playbookを実行しているノード)はユーザーzakiで、リモートのawx.jp-z.jpの実行ユーザーはubuntuとなっている。

$ ansible-playbook -i inventory.ini print-env-sample.yml 

PLAY [server] ***************************************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]
ok: [awx.jp-z.jp]

TASK [debug] ****************************************************************
ok: [awx.jp-z.jp] => {
    "msg": "ubuntu"
}
ok: [localhost] => {
    "msg": "zaki"
}

ここで、実行ノードでexportを使って環境変数USERを侑ちゃんに設定して実行してみると以下の通り。
ローカル実行のタスクのみ内容が更新されており、ansible_factsで収集したリモートの環境変数には影響してないことを確認できます。

$ export USER=yu-chan
$ ansible-playbook -i inventory.ini print-env-sample.yml 

PLAY [server] ***************************************************************

TASK [Gathering Facts] ******************************************************
ok: [localhost]
ok: [awx.jp-z.jp]

TASK [debug] ****************************************************************
ok: [awx.jp-z.jp] => {
    "msg": "ubuntu"
}
ok: [localhost] => {
    "msg": "yu-chan"
}

ただしexportを使ったローカルのこの動作は「コネクションプラグインlocalを指定した場合」の話。sshを使ってローカルホストに接続した場合は「exportを使って使用中のシェルに一時的に設定した環境変数」は維持されないため、ログイン時にセットされるデフォルトの環境変数となります。

上記例ではプレイでfacts変数を収集するgather_factsを指定していますが、1つのタスクとしてfacts変数を収集することも可能。

zaki-hmkc.hatenablog.com

環境変数の指定

タスクの実行時にモジュールで使用する値として環境変数を設定するにはenvironmentディレクティブを使用します。
指定方法は以前作成した以下も参照。

zaki-hmkc.hatenablog.com

全タスクで使用するにはプレイに、タスク単体で使用するには(環境変数を使用するのが一つのタスクのみ、等)タスクと、要件にあわせて指定箇所を変更してスコープを調整できます。
記述例として、環境変数zzzの値としてhelloをプレイにセットする場合は以下の通り。

- hosts: server
  environment:
    zzz: hello

実行例

---
- hosts: server
  gather_facts: true
  gather_subset:
    - min
  environment:
    zzz: hello

  tasks:
  - debug:
      msg: "{{ lookup('env', 'zzz') }}"

  - debug:
      msg: "{{ ansible_facts.env.zzz }}"

実行結果は以下の通り。

TASK [debug] ****************************************************************
ok: [awx.jp-z.jp] => {
    "msg": ""
}
ok: [localhost] => {
    "msg": ""
}

TASK [debug] ****************************************************************
ok: [awx.jp-z.jp] => {
    "msg": "hello"
}
ok: [localhost] => {
    "msg": "hello"
}

environmentディレクティブは「タスク実行の際に環境変数を付与」する動きになるため、マネージドノード上でのタスクの実行元となるコントロールノードのAnsibleプロセスの実行には影響しない。そのため、ルックアッププラグインでは参照できないのが確認できます。(説明が難しい…伝われ…)


使いどころとしては、クラウドサービスのアクセスキーとシークレットキーや、AAPのようなミドルウェアの接続情報など、複数タスクで共通して使用する値。モジュールのパラメタだけでなく環境変数を使用することで、プレイブックの記述量を減らして可読性を上げることができます。

- hosts: aap
  remote_user: ec2-user
  gather_facts: false
  environment:
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: "{{ password }}"
    CONTROLLER_VERIFY_SSL: false

使用するモジュールで環境変数が使用可能どうかはまとまった情報があるわけでなく、各モジュールのパラメタ等に「環境変数でも指定可能」のように書かれているのを確認する。(しかない、と思う)
例えばAAPやAWXのホスト情報を編集するawx_hostモジュール(というかAWX関連の各モジュール)は、パラメタ毎に以下のような記載がある。

If value not set, will try environment variable CONTROLLER_HOST and then config files

controller_host

https://docs.ansible.com/ansible/latest/collections/awx/awx/host_module.html

AWSであればガイドのAuthenticationの項に以下の記載あり。

Authentication with the AWS-related modules is handled by either specifying your access and secret key as ENV variables or module arguments.

For environment variables:

export AWS_ACCESS_KEY_ID='AK123'
export AWS_SECRET_ACCESS_KEY='abc123'

参考

Ansibleドキュメント

docs.ansible.com

docs.ansible.com

docs.ansible.com

過去ブログ

zaki-hmkc.hatenablog.com

まとめ

  • コントロールノードの環境変数を参照するにはenvルックアッププラグイン
  • マネージドノードの環境変数を参照するにはfacts変数を収集
  • タスクに環境変数をセットするにはenvironmentディレクティブ
  • ルックアッププラグインが作用するのはAnsibleを実行するコントロールノード
  • environmentディレクティブが作用するのはタスクを実行するマネージドノード

昔よくやってた(今でも多分やってるかも)けど、コントロールノードの実行ユーザーとマネージドノードのユーザーが同一だと、「処理対象ノードのホームディレクトリ以下の〇〇で□□するタスク」とかで、ついenvルックアッププラグイン使ってホームディレクトリを取得してちゃんと動いたように見えてたけど、あれ本当は誤りなんだよね。。

[Kustomize / secretGenerator] 初期パスワードを指定したAWXのインストールとSecret作成基礎

本記事は「Ansible Advent Calendar 2022」の8日目のエントリとなります。

AAP(旧Ansible Tower)のadminの初期パスワードはインストール時に使用するインベントリファイルで指定できるので簡単ですが、AWXのadminの初期パスワードはデフォルトではランダムな文字列がK8sクラスタ上のSecretリソースに出力され、その内容をbase64デコードして確認する必要があり、慣れないとアクセスがちょっと面倒。
そこで本記事では、AWXの初期パスワードをランダムなものでなくユーザーが指定したもので構築する方法についてと、KubernetesのSecretリソースの基礎的な作成方法について紹介します。

なお、デフォルトの動作のSecret情報は、デプロイするAWXリソース名(GitHubのドキュメント通りに作業するとawx-demoになってるkind: AWXのリソースの名前)がprefixに付与されており、以下のコマンドで取り出せます。

$ kubectl get secret -n <namespace> <AWX resource name>-admin-password -o jsonpath='{.data.password}' | base64 -d

jsonpathについてはこちらも参照

zaki-hmkc.hatenablog.com

パスワードの指定方法

AWX構築のリソースのパラメタにはadmin_password_secretがあります。
ここに「初期パスワードが記されたSecretリソースのリソース名」を指定することで、デフォルトの「AWX構築時にランダムなパスワードが設定されその内容が<awx resource name>-admin-passwordに出力される」という動作でなく、「admin_password_secretパラメタで指定したSecretリソース内の文字列を初期パスワードとしてAWXが構築される」という動作になります。

つまり事前にパスワードを設定したSecretリソースを作成しておき、そのSecretリソースの名前をadmin_password_secretにセットしてデプロイすればOK、ということ。

AWXリソースを作成するマニフェスト例として以下を想定。

apiVersion: awx.ansible.com/v1beta1
kind: AWX
metadata:
  name: awx-demo
spec:
  service_type: nodeport
  ingress_type: ingress
  hostname: awx-demo.example.org

  admin_password_secret: my-awx-password   ## ☆ここで指定している「my-awx-password」という名前のSecretリソースで初期パスワードを設定する

パスワード情報を持つSecretリソースの作成

awx-operatorのインストール、初期のころはmakeコマンドを使用する形式でした(現在も使用可能)。この時は(たぶん)Secretリソースを別途作成してその名前をデプロイの際のAWXリソースのパラメタに指定する必要があり、割と面倒でした。
ですがawx-operatorバージョン0.20.0からKustomizeを使ったデプロイ方式になり、KustomizeのsecretGenerator機能で簡単にSecretリソースを作成できるので、任意の初期パスワードを設定した状態でAWXを簡単にデプロイできるようになっています。

kubectl.docs.kubernetes.io

kubectl.docs.kubernetes.io

ちなみにAWXのパスワードとして必要なSecretリソースの中身は、以下のようにpasswordというキーにパスワード文字列を指定する形式。

password: <初期パスワードに設定したい文字列>

secretGeneratorでSecretリソース作成

KustomizeのsecretGeneratorを使ってこれを作成するには、kustomization.yamlに以下を追記します。
これは初期パスワードを「curry_tabetai」にしたい場合。

secretGenerator:
- name: my-awx-password
  literals:
  - password=curry_tabetai
  options:
    disableNameSuffixHash: true

kustomization.yamlファイル内にインラインでキー=バリュー形式の設定値(要は辞書データ)を定義するにはliteralsフィールドを使用し、そこにnameフィールドで指定する名前のSecretリソースに埋め込む値をリストで記述します。(1つのSecretリソースに複数のキー=バリューの値を埋めるためにリスト形式を使用する)
また、KustomizeはSecretとConfigMapのデフォルトの動作として、ランダムなsuffixを付与して「変更時に参照しているPodも再作成させる」という動作になります。ただ今回はその動作は不要で、というよりは固定のSecretリソース名が必要なため、この動作を抑制します。そのための追加の記述がoptionsフィールドとそこで指定しているdisableNameSuffixHash: trueになります。このオプションはConfigMapの作成と共通で、以下の記事も参照。

zaki-hmkc.hatenablog.com

kustomization.yamlファイルにこの内容の記述を含めてawx-operatorをデプロイ(というかKustomizeを実行)すると以下の内容のSecretリソースが作成されます。

$ kubectl get secret -n awx
NAME              TYPE     DATA   AGE
my-awx-password   Opaque   1      19s
$ kubectl get secret -n awx my-awx-password -o yaml 
apiVersion: v1
data:
  password: Y3VycnlfdGFiZXRhaQ==
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"password":"Y3VycnlfdGFiZXRhaQ=="},"kind":"Secret","metadata":{"annotations":{},"name":"my-awx-password","namespace":"awx"},"type":"Opaque"}
  creationTimestamp: "2022-12-05T10:43:01Z"
  name: my-awx-password
  namespace: awx
  resourceVersion: "747"
  uid: 5db1ea7e-670d-4a19-bad5-b3b49204e68e
type: Opaque

base64エンコードも自動。(デコードして内容を確認できる)

$ kubectl get secret -n awx my-awx-password -o jsonpath='{.data.password}' | base64 -d
curry_tabetai

これでAWXにブラウザでアクセスし、ユーザー名はデフォルトのadmin、パスワードはcurry_tabetaiを入力すればログインできます。
RESTの実行も以下の通り。

認証情報不足

$ curl --resolve awx-demo.example.org:80:127.0.0.1 http://awx-demo.example.org/api/v2/me/
{"detail":"Authentication credentials were not provided. To establish a login session, visit /api/login/."}

認証(basic)有り

$ curl --resolve awx-demo.example.org:80:127.0.0.1 http://awx-demo.example.org/api/v2/me/ -u admin:curry_tabetai
{"count":1,"next":null,"previous":null,"results":[{"id":1,"type":"user","url":"/api/v2/users/1/","related":{"teams":"/api/v2/users/1/teams/","organizations":"/api/v2/users/1/organizations/","admin_of_organizations":"/api/v2/users/1/admin_of_organizations/","projects":"/api/v2/users/1/projects/","credentials":"/api/v2/users/1/credentials/","roles":"/api/v2/users/1/roles/","activity_stream":"/api/v2/users/1/activity_stream/","access_list":"/api/v2/users/1/access_list/","tokens":"/api/v2/users/1/tokens/","authorized_tokens":"/api/v2/users/1/authorized_tokens/","personal_tokens":"/api/v2/users/1/personal_tokens/"},"summary_fields":{"user_capabilities":{"edit":true,"delete":false}},"created":"2022-12-05T10:49:48.749434Z","modified":"2022-12-05T13:50:30.930557Z","username":"admin","first_name":"","last_name":"","email":"test@example.com","is_superuser":true,"is_system_auditor":false,"ldap_dn":"","last_login":"2022-12-05T13:50:30.930557Z","external_account":null,"auth":[]}]}

ということで、作成するkustomization.yamlとAWXカスタムリソースのマニフェストに少し定義を追加するだけで簡単に初期パスワードを設定できるようになります。
プロジェクトなどで共通パスワードを設定したい場合はこれを使うと構築と初期設定が楽になります。

例で使用した全体のkustomization.yamlファイルの内容は以下のとおり。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - github.com/ansible/awx-operator/config/default?ref=1.1.1
  - awx-demo.yml

# Set the image tags to match the git version from above
images:
  - name: quay.io/ansible/awx-operator
    newTag: 1.1.1

# Specify a custom namespace in which to install AWX
namespace: awx

secretGenerator:
- name: my-awx-password
  literals:
  - password=curry_tabetai
  options:
    disableNameSuffixHash: true

Appendix

kubectl / secretGeneratorでSecretの作成いろいろ

Secretを作成する場合の自分用メモ。
ConfigMap作成とほぼ同じで、ConfigMapの場合はだいぶ前だけど以下でまとめてる。
Podからの参照方法についてもあわせて参照。

zaki-hmkc.hatenablog.com

リテラル(インライン記述)

前述のsecretGeneratorliteralsを使ったSecret作成は、kubectlだと以下と同等。

$ kubectl create secret generic my-awx-password --from-literal password=curry_tabetai

対象Secretに複数の値(キー=バリュー形式)をセットするには、--from=literalを値の分だけ繰り返す。

キー=バリュー形式の外部ファイルから

kustomization.yamlファイル内にSecretリソースの内容をベタ書きしたくない・CLIコマンドライン引数に直接指定したくないなどの場合に、以下の内容のpassword.iniファイルを用意しておいて、

password=curry_tabetai

kubectlコマンドで前述の例と同じ内容のSecretリソースを作成するには、以下を実行。

$ kubectl create secret generic my-awx-password --from-env-file password.ini 

これに相当する外部ファイルをソースにSecretリソースを作成するkustomization.yamlファイルの内容は以下の通り。

- name: my-awx-password
  envs:
  - password.ini

似ている機能にenvではなくfilesがあるが、filesは「ファイル名=ファイルの内容」のSecretリソースを作成する(後述)

外部ファイルを丸ごと埋め込む

Podでマウントしてファイルそのものを参照したい場合などは、kubectl create secret genericコマンドで--from-fileオプションを使用する。
これで「ファイル名=ファイルの内容」のSecretが作成される。

$ kubectl create secret generic sample-secret --from-file password.ini 

secretGeneratorで同じことを行うにはfilesを使用する。

- name: sample-secret
  files:
  - password.ini

コンテナレジストリ認証情報

認証が必要なレジストリのクレデンシャル情報を作成するときに使うkubectlコマンドは以下のような感じ。

$ kubectl create secret docker-registry regcred --from-file=.dockerconfigjson=$HOME/.docker/config.json 

kubernetes.io

これに相当するsecretGeneratorはレジストリ認証情報専用の機能はないが、前述のfilesと追加でtypeの指定で作成可能。

- name: regcred
  files:
  - .dockerconfigjson=path/to/.docker/config.json
  type: kubernetes.io/dockerconfigjson

注意点として、kustomization.yamlで指定できる外部ファイルは、kustomization.yamlファイルのあるディレクトリとそのサブディレクトリのみ。(上位のディレクトリは参照できない)

その他証明書ファイルSecretの作成(kubectl create secret tls ...)についてもsecretGeneratorに専用機能はないが、secretGeneratorのリファレンスに記述例があるので、ドキュメントみれば特に問題ないはず。

デプロイ時のkustomizeコマンドは不要 (2022.12.06時点)

awx-operatorのドキュメントにはkustomizeをインストールする手順になっていますが、実はAWXのデプロイの内容であれば現状kubectl内蔵のKustomizeで事足りるので、以下のようにkubectl apply -kコマンドでデプロイできます。

$ kubectl apply -k .
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/my-awx-password created
service/awx-operator-controller-manager-metrics-service created
deployment.apps/awx-operator-controller-manager created
awx.awx.ansible.com/awx-demo created

kubectl apply -kkustomizeコマンドについても下記参照。

zaki-hmkc.hatenablog.com

環境

確認したバージョンは以下の通り

$ 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.4
Kustomize Version: v4.5.7
Server Version: v1.24.3+k3s1
  • AWX: 21.10.0
  • awx-operator: 1.1.1

[Ansible] ini形式のインベントリファイルの行コメントには注意

本記事は「Ansible Advent Calendar 2022」の5日目のエントリとなります。

ちょっと前にやらかしたインベントリファイルの行中コメントについて、詳しく調べてみた。

結論から言うと、ini形式インベントリファイルのグループ変数定義部分の行コメントは、行の先頭のみしか認識せず、行中の行コメント文字はコメントとしては認識されない。
ちなみに急いでる人は「動作概要」だけ見ればOKです。

動作概要

[server]
host1  #ansible_user=nya   # サーバー1
host2  ansible_user=wan   # サーバー2

[server:vars]
# サーバーの設定
dst_addr=172.29.0.25 # サーバーアドレス
dst_port=25          # 接続先ポート

つい書いてしまいがちなこんな記述をすると、上でいうとdst_addrの値は「172.29.0.25」ではなく「172.29.0.25 # サーバーアドレス」になってしまう。インベントリファイル的には書式エラーではないため、Ansibleを実行しこの変数を参照するタスクが実行され(上の変数の用途的に)接続エラーになるまで問題が発覚しづらい。
# サーバーの設定 と書かれているコメント行はコメントとして有効。

なお、ホストの定義部分であれば、ホスト変数部分である「キー=値」の書式のparse処理が別途あるため、その際にコメント処理される。
前述の内容であれば、host2の「ansible_user=wan」は有効で、「host1ansible_user=nya」は無視される。

インベントリ変数がどう認識されるか確認するには

ansible-inventoryコマンドを使うと簡単。

ansible-inventory -i <インベントリファイル> --list --vars

ini形式インベントリファイルの読み込み実装を調査

ここから本編(?)です。

github.com

ini形式のインベントリファイルの変数部分をparseしているのは以下の_parse_variable_definition()メソッド内。(例によってprintデバッグで発掘)

lib/ansible/plugins/inventory/ini.pyのL.280-L.282

        if '=' in line:
            (k, v) = [e.strip() for e in line.split("=", 1)]
            return (k, self._parse_value(v))

ここの処理で、キーバリューの記述の=splitしているが、特に行末のコメント処理は行っていない。

returnで呼び出している_parse_value()についても、リストや辞書形式の記述をオブジェクト化するのみでコメント処理は行っていない

ではこの_parse_variable_definition()の呼び出し元はというと、同じクラスの_parse()メソッドから。

このメソッドは、以下のようにインベントリファイルの「コメント(;#)で始まる行」を除いた箇所を行ごと処理している。strip()は、行頭と行末のスペースを除いている。

lib/ansible/plugins/inventory/ini.pyのL.162-L.165

            line = line.strip()
            # Skip empty lines and comments
            if not line or line[0] in self._COMMENT_MARKERS:
                continue

で、処理対象の行の定義がhostchildrenvar部分かを判断しながら処理しており、変数の場合の処理で前述の_parse_variable_definition()を呼び出し、変数セットをインスタンスに保持している。

lib/ansible/plugins/inventory/ini.pyのL.220-L.222

            elif state == 'vars':
                (k, v) = self._parse_variable_definition(line)
                self.inventory.set_variable(groupname, k, v)

なお、使用可能なコメント文字は、以下の通り;#の2文字。どちらも同様に動作する。

lib/ansible/plugins/inventory/ini.pyのL.93

    _COMMENT_MARKERS = frozenset((u';', u'#'))

ということで、変数部分のコメント処理は、行頭の場合のみが有効だと実装からも確認できる。

iniファイルを処理するその他の実装

WindowsのGetPrivateProfileString()

WindowsではAPI関数のGetPrivateProfileString()を使ってiniファイルを読み込める。

learn.microsoft.com

作ったアプリはこちら。

github.com

[read]押下でiniから設定値を読み込み、[write]押下でフィールド入力中のテキストをiniへ書き込む、というもの。

このアプリを使って、コメント(;)を行中にいれて設定文字列をiniファイルへ書き込んで、

[read]押下するとこの通り、コメント以降も含めて読み込まれる。
コメント文字を#にしても動作は同じ。

分かりづらかったのでCLI

で、わざわざGUIアプリを作ってみたけど、ぶっちゃけ文章と図で状態の変化を見せるのには向いてないことに気付いた(笑)ので、ソース修正してGUI無しのmain()のみのCLI版も実装。

github.com

動作概要はこの通りで、実行時に引数があればiniへ書き込み、なければiniから読み込む、というもの。

zaki@wensley% ./cli.exe foobar
zaki@wensley% ./cli.exe
ini string: [foobar]
zaki@wensley% cat config.ini
[Section]
config=foobar

行中コメントを書き込んでみると、以下のようにコメント文字以降も設定値として有効な文字列として読み込まれる。

zaki@wensley% ./cli.exe "foobar ; comment"
zaki@wensley% ./cli.exe
ini string: [foobar ; comment]
zaki@wensley% cat config.ini
[Section]
config=foobar ; comment
zaki@wensley%
zaki@wensley% ./cli.exe "foobar # comment"
zaki@wensley% cat config.ini
[Section]
config=foobar # comment
zaki@wensley% ./cli.exe
ini string: [foobar # comment]

やはり作業とログはCLIに限るな。

PythonのConfigParser

Pythonでは標準で使えるini形式の設定ファイルを扱うConfigParserクラスがある。
(Ansibleでもこれ使ってるのかと思ってたけど、自前で実装しているのね)

docs.python.org

このクラスを使ったiniファイル操作も、Windowsと同様にデフォルトでは行中のコメントは認識されない。

import configparser

ini = configparser.ConfigParser()
ini.read('sample.ini')
interpreter = ini.get('defaults', 'interpreter_python')
ssh_config = ini.get('ssh_connection', 'ssh_args')

print(interpreter)
print(ssh_config)

こんなソースに対して、下記のようにansible.cfgっぽい内容のdefaultsセクションのinterpreter_pythonと、ssh_connectionセクションのssh_argsが定義された設定ファイルを読み込むと、これまでの例と同様にコメントを含めて読み込まれる。

(master %>) [zaki@cloud-dev2 configparser]$ cat sample.ini 
[defaults]
stdout_callback = yaml
interpreter_python = /usr/bin/python3 # ver3

[ssh_connection]
ssh_args = -o UserKnownHostsFile=/dev/null ; ssh
(master %>) [zaki@cloud-dev2 configparser]$ 
(master %>) [zaki@cloud-dev2 configparser]$ 
(master %>) [zaki@cloud-dev2 configparser]$ python sample.py 
/usr/bin/python3 # ver3
-o UserKnownHostsFile=/dev/null ; ssh

ただしConfigParserクラスにはinline_comment_prefixesオプションが用意されており、以下のようにコメント文字を指定することで行中のコメントを有効にする(コメントとして処理を無視させる)ことが可能。

configparser.ConfigParser(inline_comment_prefixes=(';', '#'))

インスタンス生成時にオプション指定しておくと、行中コメントを含むiniファイルの扱いは以下の通りで、行中コメントは無視される。

(master %>) [zaki@cloud-dev2 configparser]$ python sample.py 
/usr/bin/python3
-o UserKnownHostsFile=/dev/null

余談だが古いコード(Python2系)でインスタンス生成時にSafeConfigParserを使ってると、3.12で削除される警告が表示されるので要修正。

DeprecationWarning: The SafeConfigParser class has been renamed to ConfigParser in Python 3.2. This alias will be removed in Python 3.12. Use ConfigParser directly instead.

環境

確認に使ったCのソースコードはこちら

github.com

まとめ

ということで、Ansibleのini形式インベントリファイルに限らずiniファイルのコメントは、行頭以外には使わないようにしましょう。

共用アカウント環境における個人用GitとSSHの設定

本記事は「エーピーコミュニケーションズ Advent Calendar 2022」の2日目のエントリとなります。

Gitを使う作業を個人に与えられたOSアカウントで行う分には何も不都合はないけれど、共用サーバーの共用アカウントを複数人で使用する場合、コミット時の名前やリモートリポジトリへの認証の設定を工夫しないと他の人と設定が混じるので注意が必要。

よくあるユースケースとしては、プロダクション環境やステージング環境で作業用のOSアカウントをプロジェクトメンバ毎に作らずに、運用に使用する最小限の作業・管理用アカウントしかホスト上にない、とか。場合によっては開発環境でも。
イメージとしては、作業ホストはAWSのEC2上でec2-userユーザーをプロジェクトメンバ全員で共用するような感じ。でもGitのリモートリポジトリはGitHubやGitLab上にプロジェクトメンバ個人ごとのアカウントがある、という構成。

「私はこうやってる」という話なので、こっちの方法が簡単だよーとかあったら教えてください。($HOMEを更新するのは影響が大きそうなので避けてます)

(おさらい) 普通にGitを使う場合のユーザー設定

Gitのリポジトリを作るかGitHubからリポジトリをクローンして、何も設定してない状態でコミットしようとすると、

Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

みたいなエラーメッセージが表示される。
出力されている通りにユーザー名とメールアドレスを設定するとコミット時のユーザー名・アドレスが記録されるようになるが、このコマンドを実行すると、実行ユーザーのホームディレクトリ直下の.gitconfigファイルに設定が保存される。

$ git config --global user.name zaki
$ cat $HOME/.gitconfig
[user]
        name = zaki

これはOSのアカウントが自分専用であれば何も問題ないが、前述の通り共用アカウント環境で設定すると他の利用者(例えばプロジェクトメンバ)も同じ設定になってしまうし、逆に他の利用者がその人の名前で設定しまうと、自分のコミットが他の利用者の名前になってしまい都合が非常に悪い。

(解決策1) リポジトリ単位のユーザー設定

「Gitリポジトリ作成(またはクローン)のあと」に、git configのオプションで--globalでなく--localを付与してユーザー設定することで、$HOME/.gitconfigでなく、リポジトリローカルの.git/configへ設定保存できる。

$ git config --local user.name zaki
$ cat .git/config 
:
:
[user]
        name = zaki

このファイルの設定内容は$HOME/.gitconfigより優先されるので、OSアカウントの設定にかかわらずにクローンしたリポジトリ内に設定されたユーザー名でコミットを実行できるため、他の利用者の設定と区別できる。
(※ あくまでクローンしたリポジトリは自分しか使わないという前提で…)

ただしこの方法にも事故りやすい以下の欠点がある。

  • クローンするたびにgit config --localを使ったユーザー設定が必要で面倒
  • git config --localするのを忘れてかつ、$HOME/.gitconfigに他の利用者の設定があると気付かないうちに他の利用者名でコミットしてしまう

後者については、コミット直後であれば--amend--authorを使って比較的修正は簡単だけど、いくつもコミットがある場合はrebaseが必要なのでちょっと大変。この場合は「git commit author 変更」あたりでググると情報がヒットする。

(解決策2) 環境変数を使ったGitユーザー設定

git configを使った設定の欠点を回避する方法としては、環境変数の設定が個人的にはお勧め。

  • git config --globalはもちろんgit config --localの必要もないのでクローンの度に設定する必要がない
  • $HOME/.gitconfigが存在していても設定を上書きする

設定する環境変数は以下の通り。ユーザー名とメールアドレスでCommiterとAuthorの2つずつある。
(ComitterとAuthorの違いはググってね)

  • GIT_AUTHOR_NAME
  • GIT_COMMITTER_NAME
  • GIT_AUTHOR_EMAIL
  • GIT_COMMITTER_EMAIL

ただしこの環境変数の設定を$HOME/.bashrcとかのシェル起動時に設定するファイルで行うと(全利用者が同じユーザー設定になってしまって)無意味なので、ちょっと面倒だけど「自分用の環境変数ファイルを別途作成」し、ログインのたびに読み込むようにする。
私の場合はいつもホスト上に$HOME/(自分の名前)-work/というディレクトリを作って、そこに個人用設定ファイルなどを作成してる。環境変数の場合はだいたいいつもenv.sourceというファイルに記述している。
(名前にそんなに意味はなくてsourceコマンドで読み込むから、程度。rcの方が良いかもね。実際のところsou[tab]より.の方がタイプが速いのでsourceコマンドは使ってない笑)

Gitコミット関連の環境変数はこんな感じ。

export GIT_AUTHOR_NAME=zaki
export GIT_COMMITTER_NAME=zaki
export GIT_AUTHOR_EMAIL=zaki@email.example.org
export GIT_COMMITTER_EMAIL=zaki@email.example.org

この設定が必要な作業ホストへSSH接続するクライアントが自分専用アカウント($HOME/.ssh/configを自分専用にイジれる個人用に貸与されたPCなど)なのであれば、次のような設定でSSH接続後に任意のコマンドを自動実行することもできる。

Host target
    hostname 192.168.0.0
    identityfile ~/.ssh/id_rsa
    RemoteCommand . ~/zaki-work/env.source; bash
    RequestTTY true

共用アカウント環境におけるGit + SSHの個人設定

結論から言うと、環境変数GIT_SSH_COMMANDを使って設定すれば良い感じにできる。

共用アカウント環境のコミット時のユーザー設定は前述の通り環境変数を使って良い感じに個人設定できるけど、リモートリポジトリへプッシュするときに困ったのがSSHの設定(というか秘密鍵設定)。デフォルトでは$HOME/.ssh/configを参照するし、ssh実行時に設定ファイルを変更したい場合は-Fオプションでファイル変更できるけどgit実行時にSSHの設定ファイルを指定する専用オプションもぱっと見では見当たらず。かといってHTTPSでリモートリポジトリ設定すると、認証情報をリポジトリURLへ埋め込むかpush/pullの度に毎回入力するハメになるため煩わしい。

そこで使うのが試行錯誤した結果、環境変数GIT_SSH_COMMANDを使うことで、Git実行時に参照するSSHの設定ファイル(デフォルト$HOME/.ssh/config)をカスタムするオプションを渡すことができる。
具体的には、例えばSSHの設定ファイルとしてデフォルトでなく$HOME/zaki-work/.ssh/configを使用したい場合(sshの場合であれば-F $HOME/zaki-work/.ssh/configとする場合)は、以下の通り。

export GIT_SSH_COMMAND="ssh -F $HOME/zaki-work/.ssh/config"

これで、GitコマンドからSSHを使う場合に$HOME/zaki-work/.ssh/configを参照して実行できる。
$HOME/zaki-work/.ssh/configファイルには、$HOME/.ssh/configと同じ書式で自分が使用したい秘密鍵やユーザー名などを設定すればOK。

Host github.com
    identityfile ~/zaki-work/.ssh/my_id_rsa

GIT_SSH_COMMANDに直接鍵ファイルのパスも指定できるけどssh_configファイルを指定しておくといろいろ設定の幅が広がると思う。

秘密鍵へのアクセスについては共用アカウントの利用者は全員見ることができるけどそこは妥協点(どう考えても仕方ない)。そもそも個人アカウントがあったとしても同じホストでrootに昇格できるなら条件は同じ。

その他のGit便利設定(git-prompt)

共用アカウントで「自分用環境変数ファイル」を読み込んだときにPS1環境変数を細工してプロンプトを変化させておけば、自分用の環境がセットされていることが分かりやすい。
そこで、設定自体は共用アカウント特有の内容じゃないけど、git promptを使用し便利なGitリポジトリの状態を表示するプロンプトも設定しておくと一石二鳥。
設定を共用アカウント全体でやるなら他利用者とネゴっておきましょう。

インストールは通常はGitHubにあるソースを直接ダウンロードしてるが、疎通のない環境であればコピペで作成するのも可。

curl -L https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh -o .git-prompt.sh
chmod 755 .git-prompt.sh

主な設定は以下。(.git-prompt.sh~/zaki-work以下にある場合の設定)

if [ -f ~/zaki-work/.git-prompt.sh ]; then
    source ~/zaki-work/.git-prompt.sh
    PS1='\[\e[31m\]$(__git_ps1 "(%s) ")\[\e[0m\]'"$PS1"
    GIT_PS1_SHOWDIRTYSTATE=true
    GIT_PS1_SHOWUNTRACKEDFILES=true
    GIT_PS1_SHOWSTASHSTATE=true
    GIT_PS1_SHOWCONFLICTSTATE=yes
    GIT_PS1_SHOWUPSTREAM=auto
fi

プロンプトがこのようになり、今いるブランチや、未コミットのファイルやコンフリクトの有無などを表示してくれる。

(master *%>) [zaki@cloud-dev dev]$ 

環境変数を使った主な表示フラグについては以下。

設定 効果
GIT_PS1_SHOWDIRTYSTATE unstage(*)、staged(+)のファイルの変更の有無を表示
GIT_PS1_SHOWUNTRACKEDFILES untrackedなファイルの有無を文字'%`で表示
GIT_PS1_SHOWSTASHSTATE stashの有無を文字$で表示
GIT_PS1_SHOWCONFLICTSTATE yesにすると未解決のコンフリクトが残ってる場合にCONFLICT表示
GIT_PS1_SHOWUPSTREAM autoにするとHEADとupstream(リモートリポジトリ)との差分の有無を確認できる。未pushがあるかどうか等

ちなみにtureをセットしている環境変数は、falseにするとオフになるわけでなく、「空欄で無ければ有効」という動作。無効にするには環境変数を削除するか、空白文字をセットする。

その他の設定や詳細についてはソースのコメント参照。

github.com

設定の確認

Gitの設定ファイルによる現在の設定値は以下のコマンドで確認可能。

git config -l

--global--localを付与することで、設定箇所個別に確認もできる。

git config --system -l
git config --global -l
git config --local -l

ただしあくまで設定ファイルの状態が出力されるため、環境変数による上書きはこのコマンドでは確認できない。
環境変数の設定はenv | grep -i gitなどで確認するしかない(多分)のでその点は注意。

参考

manの日本語訳ページは便利だけど古い場合が多いので気を付けましょう:)
(SSHとか便利な新機能が結構あったりするけど20世紀版とかヒットするので)

環境

  • OS: Fedora 35
  • Git: git version 2.37.3