Jaybanuan's Blog

どうせまた調べるハメになることをメモしていくブログ

GitHub Actionsでワークフローと同じリポジトリにあるComposite Actionを利用する

はじめに

GitHub Actionのワークフローの一部を再利用したくて、いくつかのステップをComposite Actionとして利用できるように切り出した。 その際に、usesの指定方法を見つけるのに苦労したので、メモしておく。

usesの記載方法

ドキュメントの以下に記載があった。

以下にドキュメントからYAMLの記述例を抜粋する。

jobs:
  my_first_job:
    steps:
      - name: Check out repository
        uses: actions/checkout@v3
      - name: Use local my-action
        uses: ./.github/actions/my-action

参考

アクションの配置場所

ドキュメントには明確には記載されていないが、YAMLの記述例を参考にすると.github/actionsに配置するのがよさそう。

PHPのデバッグ

PHPは殆ど触れたことがないが、諸事情でデバッグすることになった。 PHPに標準的なログ出力の機能があるのかどうか(そして当該Webアプリで使っているのかどうか)は、よく分からない。 とにかく、エラーが発生したコンテキストが分かればいい。

デバッグ対象のWebアプリはExceptionを握りつぶすような実装になっているので、catch句のところにfile_put_contents()の呼び出しを加えて、例外の情報を独自にファイル出力するようにした。

catch(Exception $e) {
    file_put_contents('/tmp/log.txt', $e, FILE_APPEND | LOCK_EX);
}

systemdの管理下にあるApache HTTPDでファイル出力したためか、ログは/tmp/log.txtには出力されず、/tmp/systemd-private-f7be22cd34b84aeea9431ded8ca53541-php-fpm.service-8WLt2f/tmp/log.txtというファイルに出力されていた。 このファイルの内容を確認して、何とか原因の特定に成功した。

ちなみに、Exceptionには__toString()というメソッドが定義されていて、これはJavaでいうtoString()Pythonでいう__str__()にあたるもののように見えた。 なので、文字列が期待される場所で例外を引き渡すと、自動的に文字列化してくれるのではと推測する。

参考

www.php.net

www.php.net

www.php.net

Docker Composeの仕様

Docker Composeの仕様は、Compose Specとして以下の場所で標準化されている。

github.com

Compose Specの実装はDocker Compose、Kompose、Nerdctlなど幾つかあり、その中でもDocker ComposeはReference Implementation (参照実装) という位置づけのようだ。

Docker ComposeのマニュアルはSwarm modeなどの拡張にも言及していて、素のDocker Composeを使いたいときはマニュアルとしては少し読みづらいので、そういった場合はCompose Specを参照するのがよいのかも。

コンテナで簡易NFSサーバを構築

はじめに

評価用にNFSサーバが必要になったため、コンテナで構築できないか調べてみた。

環境

$ cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Ubuntu 22.04 LTS"

$ uname -srvm
Linux 5.15.0-25-generic #25-Ubuntu SMP Wed Mar 30 15:54:22 UTC 2022 x86_64

$ docker version
Client: Docker Engine - Community
 Version:           20.10.14
(略)
Server: Docker Engine - Community
 Engine:
  Version:          20.10.14
(略)

利用したコンテナイメージ

DockerHubで公開されている以下を利用。

更新が2019年と古いが、NFSは枯れた技術なので数年前のものでも機能的には問題ないはず。

NFSサーバの構築

(1) ディレクトリの準備

まず、今回の調査の作業用として、ホスト側に以下のディレクトリを準備した。

/
`-- nfs
    `-- nfs-root

ホスト側の/nfs/nfs-rootを、コンテナ側の/exportにバインドマウントし、これをNFSで共有する。 ホスト側の/nfsは、後述するdocker-compose.ymlの配置場所にしている。

ディレクトリの作成のために、以下のコマンドを実行する。

$ sudo mkdir -p /nfs/nfs-root

また、後ほどNFSマウントができたかどうかを確認するために、/nfs/nfs-rootの中にファイルを一つ作成しておく。

$ sudo echo "hello, world!" > /nfs/nfs-root/greeting.txt

(2) docker-compose.ymlの作成

以下の内容でdocker-compose.ymlを作成し、/nfsに配置しておく。 一部ドキュメントどおりでは動かなかった部分があり手を加えているが、詳細は後述する。

version: "3.8"
services:
  "nfs-server":
    image: erichough/nfs-server
    privileged: true
#    cap_add:
#      - SYS_ADMIN
#      - SYS_MODULE
    ports:
      - "2049:2049"
    environment:
      NFS_EXPORT_0: "/export *(rw,sync,all_squash,no_subtree_check,fsid=0)"
    volumes:
      - /nfs/nfs-root:/export
      - /lib/modules:/lib/modules:ro

(3) NFSサーバの起動

以下のコマンドを実行して、NFSサーバのコンテナを起動する。

$ cd /nfs
$ docker-compose up -d

動作確認

(1) ホストからマウント

適当なディレクト/nfs-testを作成して、マウントを試してみる。

$ sudo apt update
$ sudo apt install nfs-common
$ sudo mkdir /nfs-test
$ sudo mount -v -t nfs4 [ホストマシンのIP]:/ /nfs-test

マウントに成功すると、以下のようにgreeting.txtを読み込むことができる。

$ cat /nfs-test/greeting.txt
hello, world!

(2) コンテナからマウント

まずはUbuntuのコンテナを起動する。 ネットワークはDocker Composeが作成したnfs_defaultを利用する。

$ docker run -it --privileged --net nfs_default ubuntu:latest /bin/bash

コンテナ起動後、コンテナの中に先ほどと同様に適当なディレクト/nfs-testを作成して、マウントを試してみる。

$ apt update
$ apt install nfs-common
$ mkdir /nfs-test
$ mount -v -t nfs4 nfs-server:/ /nfs-test

マウントに成功すると、以下のようにgreeting.txtを読み込むことができる。

$ cat /nfs-test/greeting.txt
hello, world!

ちなみに、コンテナ起動時にオプション--privilegedを付与しておかないと、以下のようなエラーが出てマウントに失敗する。

$ mount -t nfs4 nfs-server:/ /mnt
mount.nfs4: Operation not permitted

参考

ドキュメントどおりでは動かなかった部分

コンテナイメージerichough/nfs-serverのドキュメントは、ドキュメント内のリンクの都合上、DockerHubよりもGitHubの方を見たほうがよい。 主に次の2つのドキュメントを参照した。

github.com

github.com

ここで、コンテナに付与する権限について--privilegedではなく--cap-addを推奨しているが、ドキュメントどおりにSYS_ADMINSYS_MODULEを付与しても権限が足りずに起動に失敗する。 他に何が必要なのかを調べる時間がないので、--privilegedを利用した。

クライアント側でも--privilegedがないとマウントに失敗するので、NFSLinuxカーネルと密結合しているように思う。

NFSのバージョンについて

NFS v3はRPCやロックなどの様々なサービス(デーモン)を動的に組み合わせて実現しているため、設定が複雑で単にコンテナのポートをホスト側で公開するだけでは動かない。 そのため、コンテナでのNFS v3サーバの構築はハードルが高い。

一方、NFS v4はその辺りが改善されていて、ポート2049にアクセスできればNFSの利用が可能であり、コンテナでのNFS v4サーバの構築は比較的ハードルが低い。 とはいえ、--privileged等を利用した権限付与が必要であり、コンテナを運用する際のセキュリティポリシー次第では、NFS v4は利用できない可能性がある。

複数のGitのユーザを使い分ける

はじめに

複数のGitサーバ/サービスを利用する場合、Gitのユーザの切り替えが必要になる。 適切に切り替えを行わないと、誤ったユーザでコミットしてしまって、履歴を汚してしまう。 できるだけこの失敗を防ぐために、やっていることのメモを残しておく。

ユーザをグローバルに設定しない

あえてグローバルにユーザを設定せず、リポジトリごとにローカルにユーザを設定する。 これで、「デフォルト(=グローバルな設定)のユーザでコミットしてしまった」というミスは防げる。

示すまでもないが、各リポジトリ内で以下を設定することになる。

$ git config --local user.name "jaybanuan"
$ git config --local user.email ”jaybanuan@example.com”

Bashのプロンプトにユーザ名を表示する

カレントディレクトリのリポジトリにどのユーザが設定されているかを、Bashのプロンプトに表示しておく。 これで、「想定とは違うユーザでコミットしてしまった」というミスを減らすことができる。

やりたいことを具体的に説明すると、通常は以下のようなプロンプトだが、

jaybanuan@devpc:~/src $ 

以下のようにgit cloneしたリポジトリに移動すると、

jaybanuan@devpc:~/src $ git clone https://host/path/to/myapp.git
jaybanuan@devpc:~/src $ cd myapp

以下のように、末尾にgitのユーザ名@ブランチ名が付加されたプロンプトに変える。

jaybanuan@devpc:~/src/myapp (jaybanuan@main)$

ユーザ名が設定されていない場合は以下のようにNO-USER-NAMEと表示し、git configでユーザ名を設定すると上記と同様にユーザ名が表示されるようになる。

jaybanuan@devpc:~/src/myapp (NO-USER-NAME@main)$
jaybanuan@devpc:~/src/myapp (NO-USER-NAME@main)$ git config --local user.name jaybanuan
jaybanuan@devpc:~/src/myapp (jaybanuan@main)$

これを実現するために、以下のスクリプトを"~/.bashrc"に付け加えておく。

get_git_info_for_ps1() {
    local GIT_INFO=$(__git_ps1 "%s")
    if [ -n "$GIT_INFO" ]; then
        local GIT_USER_NAME=$(git config --get user.name)
        if [ -z "$GIT_USER_NAME" ]; then
            USERNAME="NO-USER-NAME"            
        fi
        
        echo " ($USERNAME@$GIT_INFO)"
    fi
}

PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\#033[00m\]\[\033[36m\]$(get_git_info_for_ps1)\[\033[00m\]\$ '

複数のユーザを管理できるCredential Helperを使う

gitのパスワード管理にはCredential Helperを利用すると入力の手間が省けて楽である。 しかしながら、Credential Helperの設計思想的には、ある1人のユーザが複数のGitサーバを使い分けるという一対多の関係を想定していると思われる。 現実にはプロジェクトやポジションによってユーザを使い分けることがあり、その場合は多対多の関係になる。

サードパーティ製(GitHub製)のCredential Helperにgit-credential-manager-coreというものがあり、名前空間という独自機能をもっている。 この名前空間を利用してユーザごとに管理領域を分けることで、多対多の管理を実現することができる。

github.com

具体的には、git-credential-manager-coreをインストールした後、cloneしたgitのリポジトリに入って以下のようなコマンドを実行すれば良い。

$ git config --local credential.credentialStore secretservice
$ git config --local credential.namespace jaybanuan

参考

リポジトリをcloneするたびに適切にgit configを実行するのが面倒だったので、スクリプト化した。

github.com

スクリプトはgitのサブコマンドの仕様に沿ったファイル名にしてあるので、パスの通っているディレクトリに配置しておけば、以下のように1コマンドで設定ができる。

$ git user jaybanuan

Minikubeを構築して、ローカルのコンテナイメージをデプロイする

はじめに

コンテナの開発環境を構築する際のメモ。 Minikubeを構築して、ローカルのコンテナイメージをデプロイするまでの手順を残しておく。

環境

$ cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Ubuntu 20.04.4 LTS"

$ uname -srvm
Linux 5.13.0-39-generic #44~20.04.1-Ubuntu SMP Thu Mar 24 16:43:35 UTC 2022 x86_64

$ docker version
Client: Docker Engine - Community
 Version:           20.10.14
(略)
Server: Docker Engine - Community
 Engine:
  Version:          20.10.14
(略)

手順

(1) Minikubeのインストール

インストール方法はいくつかあるが、今回はDebian packageを利用する。 以下のコマンドを実行。

$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_latest_amd64.deb
$ sudo dpkg -i minikube_latest_amd64.deb

詳細はこちら。

minikube.sigs.k8s.io

(2) kubectlのインストール

インストール方法はいくつかあるが、今回はsnapを利用する。 以下のコマンドを実行。

$ sudo snap install kubectl --classic

詳細はこちら。

kubernetes.io

(3) Minikubeの起動

以下のコマンドを実行。

$ minikube start
😄  Ubuntu 20.04 上の minikube v1.25.2
✨  docker ドライバーが自動的に選択されました。他の選択肢: kvm2, ssh, none
👍  minikube クラスター中のコントロールプレーンの minikube ノードを起動しています
🚜  ベースイメージを取得しています...
💾  Kubernetes v1.23.3 のダウンロードの準備をしています
    > gcr.io/k8s-minikube/kicbase: 379.06 MiB / 379.06 MiB  100.00% 169.59 KiB 
    > preloaded-images-k8s-v17-v1...: 505.68 MiB / 505.68 MiB  100.00% 220.72 K
🔥  docker container (CPUs=2, Memory=3900MB) を作成しています...
🐳  Docker 20.10.12 で Kubernetes v1.23.3 を準備しています...
    ▪ kubelet.housekeeping-interval=5m
    ▪ 証明書と鍵を作成しています...
    ▪ コントロールプレーンを起動しています...
    ▪ RBAC のルールを設定中です...
🔎  Kubernetes コンポーネントを検証しています...
    ▪ gcr.io/k8s-minikube/storage-provisioner:v5 イメージを使用しています
🌟  有効なアドオン: storage-provisioner, default-storageclass
🏄  完了しました! kubectl が「"minikube"」クラスタと「"default"」ネームスペースを使用するよう構成されました

(4) ダッシュボードで動作確認

以下のコマンドを実行。

$ minikube dashboard
🔌  ダッシュボードを有効化しています...
    ▪ kubernetesui/metrics-scraper:v1.0.7 イメージを使用しています
    ▪ kubernetesui/dashboard:v2.3.1 イメージを使用しています
🤔  ダッシュボードの状態を検証しています...
🚀  プロキシーを起動しています...
🤔  プロキシーの状態を検証しています...
🎉  デフォルトブラウザーで http://127.0.0.1:35159/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ を開いています...
既存のブラウザ セッションで開いています。

デフォルトのブラウザが自動的に起動されて、以下のようにダッシュボードが表示される。 まだ何もデプロイしていないので「表示するものがありません」とメッセージが出ている。

f:id:redj:20220406115027p:plain

(5) テスト用のWebサーバのイメージを作成

まず、作成するコンテナイメージをMinikubeが認識できるようにしなければならない。 その方法はいくつかあるが、今回は「docker-env command」という方法を採用する。 以下のコマンドを実行する。

$ eval $(minikube docker-env)

詳細はこちら。

minikube.sigs.k8s.io

次に、以下の内容でDockerfileを作成する。 Ningxのドキュメントルートに、hello, world! を表示するだけのHTMLを配置している。

FROM nginx:1.21
RUN echo "<!DOCTYPE html><html><body>hello, world!</body></html>" > /usr/share/nginx/html/index.html

そして、以下のコマンドを実行して、コンテナイメージを作成する。

$ docker build --tag test-web:1.0.0 .

(6) Minikubeにデプロイ

以下の内容でmanifest.ymlを作成する。

---
apiVersion: v1
kind: Service
metadata:
  name: test-web-service
spec:
  type: NodePort
  selector:
    app: test-web
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-web-deployment
  labels:
    app: test-web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-web
  template:
    metadata:
      labels:
        app: test-web
    spec:
      containers:
      - name: test-web
        image: test-web:1.0.0
        ports:
        - containerPort: 80

このマニフェストファイルをMinikubeに適用する。

$ kubectl apply -f manifest.yml 

(7) 動作確認

以下のコマンドを実行して、アクセス用のURLを取得する。

$ minikube service test-web-service --url
http://192.168.49.2:32219

WebブラウザでこのURLにアクセスすると、以下のようにhello, world!が表示される。

f:id:redj:20220406115152p:plain

ダッシュボードを確認すると、デプロイメント、ポッド、レプリカセットが作成されていることを確認できる。

f:id:redj:20220406115249p:plain