kustomize buildを実行して得たmanifestの差分ををPull Request上で確認する
この記事は GMOペパボエンジニアカレンダー の19日目の記事です。諸事情によりバタバタしてしまい、記事の公開がめちゃくちゃ遅くなりました🙏
普段運用しているKubernetesクラスターでは、デプロイするmanifestは環境毎の差分を管理しやすくするために kustomize を用いています。 共通のmanifestをベースに環境毎にpatchを当てたりすることが多いのですが、これが複雑になってくると、少しの変更でも影響範囲を把握するのが大変だったりするので、毎回手元でkustomize buildを実行して差分を確認するようにしていました。
例えば私の場合だと、
- 現在のブランチでkustomize buildを実行して得たmanifestを何かしらのファイルに書き込んでおく。
- 指定したブランチ(mainブランチとか)に切り替えて、再度kustomize buildを実行し、1 との差分をdiffコマンド等で確認する。
といったようなことを行なっていました。(もっと良い方法があるかもしれないですが...)
これをPull Request上で確認できるとめちゃくちゃ嬉しいなと思い、何か良い感じのツールが無いか探していたところ git-kustomize-diff というツールがかなり便利そうだったので、今回はGithub ActionsでPull Requestをトリガーに、kustomize buildを実行して得たmanifestの差分をPull Request上で確認できるようにする方法をご紹介します。
こんな感じでブランチ間の差分をPull Request上で確認することができます。
サンプルPR: Test pull request by rnakamine · Pull Request #2 · rnakamine/sample-git-kustomize-diff · GitHub

diffを取得する
上記でも挙げたように、差分の取得には git-kustomize-diff を使用します。これは、--base(デフォルトはorigin/main), --target(デフォルトはcurrent branch)で指定したそれぞれのブランチでkustomize buildを行い、そのdiffを最終的にはmarkdown形式で標準出力に出してくれます。
$ git-kustomize-diff run --include ".*/overlays/*/"
カレントディレクトリ内の kustomization.yaml (kustomization.yml) に対してkustomize buildが行われます。--includeオプションや--excludeオプションを使用することで、正規表現を用いてkustomize buildの対象を絞り込むことができます。
今回の場合だと、--include ".*/overlays/*/"と指定して、*/overlays/配下のkustomzation.yamlのみkustomize buildを実行するようにしています。
結果をPull Requestにコメントする
Pull Request上へのコメントは ghput を使用します。
ghput pr-comment コマンドでは、標準入力をPull Requestにコメントしてくれるので、git-kustomize-diffからの出力をそのままパイプで渡してあげることで、Pull Request上で差分を確認することができるようになります。
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} ghput pr-comment --owner rnakamine --repo sample-git-kustomize-diff --number $NUMBER
ghputの使い方はこちらを参考にさせてもらいました。
Github Actionsで実行する
これらを、Github ActionsでPull Requestをトリガーに実行させるようにしています。ワークフローのファイルはこんな感じになっています。
name: CI on: pull_request jobs: ci: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v2 with: ref: ${{ github.head_ref }} clean: false fetch-depth: 0 - name: setup git-kustomize-diff run: | curl -sLf https://github.com/dtaniwaki/git-kustomize-diff/releases/download/v0.1.8/git-kustomize-diff_linux_x86_64.tar.gz -o git-kustomize-diff.tar.gz && \ tar xzf git-kustomize-diff.tar.gz && \ mv git-kustomize-diff /usr/local/bin && \ rm git-kustomize-diff.tar.gz - name: setup ghput run: | curl -sLf https://github.com/k1LoW/ghput/releases/download/v0.12.2/ghput_v0.12.2_linux_amd64.tar.gz -o ghput.tar.gz && \ tar xzf ghput.tar.gz && \ mv ghput /usr/local/bin && \ rm ghput.tar.gz - name: kustomize diff run: | NUMBER=$(echo $GITHUB_REF | sed -e 's/[^0-9]//g') # Pull Requestの番号を取得する git-kustomize-diff run --base origin/$GITHUB_BASE_REF --target origin/$GITHUB_HEAD_REF --include ".*/overlays/*/" | \ GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} ghput pr-comment --owner rnakamine --repo sample-git-kustomize-diff --number $NUMBER
ワークフローのトリガーにpull_requestイベントを使っているのは、PR番号を取得するためです。(pushイベントだと取得できないはず...)
感想
git-kustomize-diff を使うことで、環境毎・リソース毎にブランチ間の差分を一発で見れるようになるので、自身での差分の確認も楽になるのですが ghput と組み合わせることにより、GithubのPull Reqeust上でこれらの差分が確認できるようになるので、コードレビューのコストもかなり下げられるのではないかと思いました!
istio-proxy(envoy) のアクセスログをパースして任意の形式で出力するツールを作った
この記事は CODE BASE OKINAWA Advent Calendar 2022 - Adventar の12日目の記事です。
今日は、普段のお仕事を少しだけ楽にするツールを書いたので、紹介していきたいと思います。
普段運用しているKubernetesクラスターでIstioを導入しています。
手元から Kubectl logs コマンドや stern コマンドを使って、istio-proxyのアクセスログを確認したい場面が多々あるのですが、出力されるログの形式が以下のようになっており、どれがどの値か分からん!となって、よくログとドキュメントを行ったり来たりしています。
$ kubectl logs -l app=sleep -c istio-proxy [2020-11-25T21:26:18.409Z] "GET /status/418 HTTP/1.1" 418 - via_upstream - "-" 0 135 4 4 "-" "curl/7.73.0-DEV" "84961386-6d84-929d-98bd-c5aee93b5c88" "httpbin:8000" "10.44.1.27:80" outbound|8000||httpbin.foo.svc.cluster.local 10.44.1.23:37652 10.0.45.184:8000 10.44.1.23:46520 - default
ref: Istio / Envoy Access Logs
そこで、上記のログのパースして、別の形式で見れると何かと便利じゃないかなぁと思い、Goを用いてistio-axslog というツールを作成しました。
使い方
ログを標準入力から受け取り、任意の形式で出力することができます。
echo '<Istio Proxy Access Log>' | istio-axslog [flags]
現在サポートされている出力形式は、jsonとltsvです。
--output json (デフォルト)
$ kubectl logs -l app=sleep -c istio-proxy | istio-axslog {"start_time":"2020-11-25T21:26:18.409Z","method":"GET","path":"/status/418","protocol":"HTTP/1.1","response_code":"418","response_flags":"-","response_code_details":"via_upstream","connection_termination_details":"-","upstream_transport_failure_reason":"-","bytes_received":"0","bytes_sent":"135","duration":"4","x_envoy_upstream_service_time":"4","x_forwarded_for":"-","user_agent":"curl/7.73.0-DEV","x_request_id":"84961386-6d84-929d-98bd-c5aee93b5c88","authority":"httpbin:8000","upstream_host":"10.44.1.27:80","upstream_cluster":"outbound|8000||httpbin.foo.svc.cluster.local","upstream_local_address":"10.44.1.23:37652","downstream_local_address":"10.0.45.184:8000","downstream_remote_address":"10.44.1.23:46520","requested_server_name":"-","route_name":"default"} {"start_time":"2020-11-25T21:27:20.503Z","method":"GET","path":"/status/418","protocol":"HTTP/1.1","response_code":"418","response_flags":"-","response_code_details":"via_upstream","connection_termination_details":"-","upstream_transport_failure_reason":"-","bytes_received":"0","bytes_sent":"135","duration":"4","x_envoy_upstream_service_time":"4","x_forwarded_for":"-","user_agent":"curl/7.73.0-DEV","x_request_id":"84961386-6d84-929d-98bd-c5aee93b5c88","authority":"httpbin:8000","upstream_host":"10.44.1.27:80","upstream_cluster":"outbound|8000||httpbin.foo.svc.cluster.local","upstream_local_address":"10.44.1.23:37652","downstream_local_address":"10.0.45.184:8000","downstream_remote_address":"10.44.1.23:46520","requested_server_name":"-","route_name":"default"} {"start_time":"2020-11-25T21:28:39.399Z","method":"GET","path":"/status/418","protocol":"HTTP/1.1","response_code":"418","response_flags":"-","response_code_details":"via_upstream","connection_termination_details":"-","upstream_transport_failure_reason":"-","bytes_received":"0","bytes_sent":"135","duration":"4","x_envoy_upstream_service_time":"4","x_forwarded_for":"-","user_agent":"curl/7.73.0-DEV","x_request_id":"84961386-6d84-929d-98bd-c5aee93b5c88","authority":"httpbin:8000","upstream_host":"10.44.1.27:80","upstream_cluster":"outbound|8000||httpbin.foo.svc.cluster.local","upstream_local_address":"10.44.1.23:37652","downstream_local_address":"10.0.45.184:8000","downstream_remote_address":"10.44.1.23:46520","requested_server_name":"-","route_name":"default"} ...
--output ltsv
$ kubectl logs -l app=sleep -c istio-proxy | istio-axslog -o ltsv start_time:2020-11-25T21:26:18.409Z method:GET path:/status/418 protocol:HTTP/1.1 response_code:418 response_flags:- response_code_details:via_upstream connection_termination_details:- upstream_transport_failure_reason:- bytes_received:0 bytes_sent:135 duration:4 x_envoy_upstream_service_time:4 x_forwarded_for:- user_agent:curl/7.73.0-DEV x_request_id:84961386-6d84-929d-98bd-c5aee93b5c88 authority:httpbin:8000 upstream_host:10.44.1.27:80 upstream_cluster:outbound|8000||httpbin.foo.svc.cluster.local upstream_local_address:10.44.1.23:37652 downstream_local_address:10.0.45.184:8000 downstream_remote_address:10.44.1.23:46520 requested_server_name:- route_name:default start_time:2020-11-25T21:27:19.409Z method:GET path:/status/418 protocol:HTTP/1.1 response_code:418 response_flags:- response_code_details:via_upstream connection_termination_details:- upstream_transport_failure_reason:- bytes_received:0 bytes_sent:135 duration:4 x_envoy_upstream_service_time:4 x_forwarded_for:- user_agent:curl/7.73.0-DEV x_request_id:84961386-6d84-929d-98bd-c5aee93b5c88 authority:httpbin:8000 upstream_host:10.44.1.27:80 upstream_cluster:outbound|8000||httpbin.foo.svc.cluster.local upstream_local_address:10.44.1.23:37652 downstream_local_address:10.0.45.184:8000 downstream_remote_address:10.44.1.23:46520 requested_server_name:- route_name:default start_time:2020-11-25T21:29:18.129Z method:GET path:/status/418 protocol:HTTP/1.1 response_code:418 response_flags:- response_code_details:via_upstream connection_termination_details:- upstream_transport_failure_reason:- bytes_received:0 bytes_sent:135 duration:4 x_envoy_upstream_service_time:4 x_forwarded_for:- user_agent:curl/7.73.0-DEV x_request_id:84961386-6d84-929d-98bd-c5aee93b5c88 authority:httpbin:8000 upstream_host:10.44.1.27:80 upstream_cluster:outbound|8000||httpbin.foo.svc.cluster.local upstream_local_address:10.44.1.23:37652 downstream_local_address:10.0.45.184:8000 downstream_remote_address:10.44.1.23:46520 requested_server_name:- route_name:default ...
もちろん、jq や lltsv などの他のツールと組み合わせて使うこともできます。
仕組み
仕組みはめちゃくちゃシンプルで、ログを標準入力から受け取り、1行ずつ正規表現を用いてパースして、 構造体 を生成しています。ログはこちらの Default access log format を元にパースしています。
構造体にしてしまえば、あとは標準パッケージや外部のパッケージなんかを使って構造体を簡単にjsonなりltsvの形式に変換することができるので、それらを繰り返し標準出力に出すだけです。めちゃくちゃシンプル。
最後に
インストールもmacOSならhomebrewからインストールできるようにしており、他の環境でも こちら からバイナリを落としてくるだけで使えるようになっています。
冒頭でも挙げたように、istio-proxyのアクセスログがパッと分かりづらいと思っている方がいらっしゃいましたら、是非使ってみてください!(もちろんフィードバックやPRもお待ちしております)
Goを用いてTCPソケット通信を学ぶ
この記事は GMOペパボエンジニア Advent Calendar 2021 - Adventar の6日目の記事です。
昨日は buty4649 さんの業務で使っているPCをLinuxデスクトップにしてから3年半が経った でした。 これまでデスクトップPCはWindowsかMacしか使ったことがなかったので、Linuxデスクトップの話は個人的にはかなり新鮮な内容でした。その他にも普段開発で利用しているアプリケーションの紹介もされていて、最近リモートで働く中でなかなか自分以外の開発環境を見る機会がほとんど無いので、めちゃくちゃ参考になりました!
はじめに
以前同僚がこんな記事を書いてて
ん....そもそもソケット自体よく分かっていない....どうしよう....って状態だったので、このタイミングで改めて学習し直しました。 今回はTCPソケット通信の処理の流れだったり、その中で使われているシステムコールについて見ていきながら、最後にGoを用いて簡単なTCPサーバ / クライアントを動かしてみようと思います。
TCPソケットとは
TCP/IP アプリケーションを作成するための抽象化されたインターフェースのことで、ユーザープロセスとTCP/IPを繋ぐ出入り口になる。 お互いのソケットをネットワークを通して接続し,その口を通して通信を実現することができます。 ソケットを利用することで、TCPの処理はOS側で行ってくれることによって、アプリケーション側では意識しなくてもよくなります。
ソケット自体は以下の2種類があります。
※訂正: 下記の2つを含め他にもたくさんの種類のソケットがあるそうです。( Man page of SOCKET ) ご指摘頂きありがとうございます!
INETドメイン(ネットワークソケット)
- 異なるマシンで動作しているプロセス間を行うためのソケット。
- IPアドレス + ポートを利用する (例: 192.168.10.10:9000)
- ループバックアドレスを使えば同じマシン内でも通信できるが、UNIXドメインを使う方が高速
今回はINETドメインに着目してTCP/IPにおけるソケット通信の流れを見ていきます。
ソケット通信の基本となる流れは以下のようになっています。 その中でいくつかのシステムコールを呼び出して通信を行なっています。

socket()
ソケットを作成する。(サーバ/クライアント)
bind()
作成したソケットに対してポートをなどを割り当てる。(サーバ)
listen()
ソケットを接続待ちの状態にする。(サーバ)
accept()
クライアントから来た接続要求に対してソケットへの接続を受ける。(サーバ)
connect()
サーバに対してソケット接続を要求する。(クライアント)
read() / write()
クライアント or サーバから来たデータの受信と送信を行う。(サーバ/クライアント)
Man page of READ Man page of WRITE
close()
接続を切断する。(サーバ/クライアント)
TCP接続の確立(3 Way Handshake)については以下の記事がとても参考になりました! milestone-of-se.nesuke.com
簡単なTCP サーバ / クライアントを動かしてみる
Goを用いてTCP通信を行う際に、標準で用意されているnetパッケージというものがあります。 今回はこちらを使用してサーバ⇄クライアント間でTCP通信を行なっていきます。
ここではクライアント側で書き込んだ文字列をサーバ側で受け取り、それをそっくりそのままクライアントに返すだけの構成になっています。
サーバ側のコード
package main import ( "io" "log" "net" ) func main() { ln, err := net.Listen("tcp", ":8080") if err != nil { log.Fatal(err) } for { conn, err := ln.Accept() if err != nil { log.Fatal(err) } io.Copy(conn, conn) conn.Close() } }
かなりざっくりコードを追うと、net.Listen()メソッドの内部では、上記で挙げたsocket()、bind()、listen()といったシステムコールの呼び出しを良い感じに行ってくれます。
net.Listen()で取得したTCPListenerに対してAccept()メソッドを呼び出すことで、クライアントからの接続を待ち受けることができます。
実際にどんなシステムコールがどのような順番で呼び出されているかは、こちらを参考にさせて頂きました。
クライアント側
package main import ( "fmt" "log" "net" ) func main() { conn, err := net.Dial("tcp", "localhost:8080") if err != nil { log.Fatal(err) } str := "Hello!" _, err = conn.Write([]byte(str)) if err != nil { log.Fatal(err) } buf := make([]byte, 1024) count, err := conn.Read(buf) if err != nil { log.Fatal(err) } fmt.Println(string(buf[:count])) }
クライアントも同様にnet.Dial()メソッドを呼び出すことで、内部的には良い感じにsocket()、connect()といったシステムコールを呼び出し、ソケットの作成やリモートのソケットに対して3 Way Handshakeを実施してコネクションを確立するといったことを行っています。
net.Dial()から取得されるTCPConnは、Connインターフェース( net package - net - pkg.go.dev )を満たしており、Write()やRead()が実装されていることから
コネクションへの読み書きは上記のサンプルコードのようにconn.Write()やconn.Read()を用いてバイト列を読み書きすることで、データの送受信を行うことができます。
終わりに
ソケット通信って聞いたことあるけど、実際にどんなことをしているかほぼ雰囲気でしか分かっていなかったのですが、今回(TCPだけですが...)処理の流れを掴むことができたり、中でどんなシステムコールが呼ばれているかなど知る良いきっかけになりました。
また、絶賛勉強中のGoのサンプルコードを用いることで、netパッケージに内部を少しだけ追うことができたので、かなり勉強になりました!
netパッケージについてさらに深く読んでみたいのであれば、このあたりがとても参考になると思います。
zenn.dev
アドベントカレンダー7日目は しばっちさん です!宜しくお願いします!
【Docker/Kubernetes 実践コンテナ開発入門】を読んだ
今年は業務でKubernetesをがっつり扱っていくことになると思うので、先に概要だけでも掴みたく、いくつかKubernetes関連の書籍を読んでいくことにした。
今回は【Docker/Kubernetes 実践コンテナ開発入門】を読んだ。Dockerの基礎からKubernetesの入門部分までざっくり概要を掴むことができた。その中で気になった部分をメモとして残しておく。Docker Swarmは多分使わないだろうから、そこについては読み飛ばすことにした。
目次
- 1.Dockerの基礎
- 1.1 Dockerとは
- 1.2 Dockerを利用する意義
- 1.3 ローカルDocker環境を構築する
- 2.Dockerコンテナのデプロイ
- 2.1 コンテナでアプリケーションを実行する
- 2.2 Dockerイメージの操作
- 2.3 Dockerコンテナの操作
- 2.4 運用管理向けコマンド
- 2.5 Docker Composeでマルチコンテナを実行する
- 2.6 Composeによる複数コンテナの実行
- 3.実用的なコンテナの構築とデプロイ
- 3.1 アプリケーションとコンテナの粒度
- 3.2 コンテナのポータビリティ
- 3.3 Dockerフレンドリなアプリケーション
- 3.4 永続化データをどう扱うか
- 3.5 コンテナ配置戦略
- 4.Swarmによる実践的なアプリケーション構築
- 4.1 Webアプリケーションの構成
- 4.2 MySQL Serviceの構築
- 4.3 API Serviceの構築
- 4.4 Nginxの構築
- 4.5 Webの構築
- 4.6 コンテナオーケストレーションによる開発スタイル
- 5.Kubernetes入門
- 5.1 Kubernetesとは
- 5.2 ローカル環境でKubernetesを実行する
- 5.3 Kubernetesの概念
- 5.4 KubernetesクラスタとNode
- 5.5 Namespace
- 5.6 Pod
- 5.7 ReplicaSet
- 5.8 Deployment
- 5.9 Service
- 5.10 Ingress
- 6.Kubernetesのデプロイ・クラスタ構築
- 6.1 Google Kubernetes Engineのセットアップ
- 6.2 GKE上にTODOアプリケーションを構築する
- 6.3 Master Slave構成のMySQLをGKE上に構築する
- 6.4 TODO APIをGKE上に構築する
- 6.5 TODO WebアプリケーションをGKE上に構築する
- 6.6 IngressでWebアプリケーションをインターネットに公開する
- 6.7 オンプレミス環境でのKubernetesクラスタの構築
- 6.8 kubesprayでKubernetesクラスタを構築する
- 7.Kubernetesの発展的な利用
- 7.1 Kubernetesの様々なリソース
- 7.2 ユーザー管理とRole-Based Access Control (RBAC)
- 7.3 Helm
- 7.4 Kubernetesにおけるデプロイ戦略
- 8.コンテナの運用
- 8.1 ロギングの運用
- 8.2 Dockerホストやデーモンの運用
- 8.3 障害対策
- 9.より軽量なDockerイメージを作る
- 9.1 なぜ軽量なイメージを作るべきなのか
- 9.2 軽量なベースイメージ
- 9.3 軽量なDockerイメージをつくる
- 9.4 multi-stage builds
- 10.Dockerの様々な活用方法
- Appendix-A セキュリティ
- Appendix-B Dockerでの開発を支援するツール・サービス
- Appendix-C 主要コマンドまとめ
1コンテナに1つの関心事
これを読むまでは自分の中でコンテナはある種1つのプロセスのようなもの、つまり1コンテナ=1プロセスと考えていたが、
それだと無理があるケースも存在する。
例えば、定期ジョブを実行するようなアプリケーションがあるとして、スケジューラーとアプリケーションが一体型となっていれば、
1つのコンテナで実現できそう(1コンテナ=1プロセス)だが、cronを使うなど外部にスケジューラを持つ場合、
1コンテナ=1プロセスを実現しようとすると、コンテナ間の通信を行う必要があり、そのためにAPIを準備したり等色々あるため、構成が複雑になってしまう。
この場合1コンテナ=1プロセスより、無理せず1つのコンテナで複数プロセスを実行する方がシンプルに完結するケースも多い。
Webアプリケーションのスタックにおいては、リバースプロキシ、アプリケーション、データストアがそれぞれ独立して役割を果たし、全体を構成することが多いと思うので、この粒度でのコンテナ化は違和感なさそう。
Docker化しやすいアプリケーションの特徴
Dockerフレンドリなアプリケーションはコンテナ化の恩恵を最大限に受けられる。
そのためには、ポータビリティの高いアプリケーションの構築が必要となっており、 再利用性や柔軟性を持たせ、それによって動作を制御できるようにしておくことが重要。
Dockerコンテナとして実行されるアプリケーションの挙動を制御するには以下のような手法が考えられる。
データの永続化手法としてのData Volumeコンテナ
Data Volume自体の仕組みよく使っていたが、それをコンテナとして扱う手法は今回初めて知った。
Data Volumeコンテナによって、Data Volume(コンテナ⇄ホスト間でのディレクトリ共有)への操作がカプセル化され、ホストをあまり意識せずにData Volumeを使用できるメリットがあるようだ。
Kubernetesの各種リソース・コンポーネント
-
- Kubernetesの様々なリソースを管理する集合体
Node
Namespace
Pod
- コンテナの集合体
- 少なくとも1つのコンテナを持つ
ReplicaSet
- 同じ仕様のPodを複数生成・管理するためのリソース
Deployment
- アプリケーションデプロイの基本単位となるリソース
- ReplicaSetを管理・操作するためのリソース
Service
- Podの集合に対する経路やサービスディスカバリを提供するためのリソース
- サービスディスカバリ
- クライアントから一貫した名前でアクセスできるようにする仕組み
- サービスディスカバリ
- Podの集合に対する経路やサービスディスカバリを提供するためのリソース
Job
- 1つ以上のPodを作成し、指定された数のPodが正常に完了するまでを管理するリソース
- Webアプリケーションなどの常駐型のアプリケーションではなく、大規模な計算やバッチ処理などに使われる
Secret
- 機密情報などの文字列をBase64にエンコードしKubernetes内で扱えるようにするためのリソース
DaemonSet
- クラスタで管理されている全てのNodeに対して必ず1つ配置されるPodを管理するためのリソース
- ログ収集用のエージェントなどを配置したいときに使われる
デプロイについて
RollingUpdate
古いバージョンのアプリケーションを実行した状態で新しいアプリケーションを起動し、準備が完了したものから徐々にサービスインしていく仕組み。
BlueGreen Deployment
新旧2つのバージョンのサーバ群(ここではコンテナ)を一気に切り替えてデプロイする手法。ロールバックも容易。
感想
Kubernetesの概要や、各リソース・コンポーネント、またはそれらの役割について、全てではないがある程度ざっくり押さえることができたと思う。 この本はKubernetes以外にも、Dockerの基礎やDocker Swarm、Dockerのセキュリティや開発を支援するツールなどにも触れていて、結構ボリュームがある内容だった。
他にもいくつか書籍を当たってみたあと、実際に手を動かして慣れていきたい。
OSSに初めてコントリビュートできて嬉しかった話
今年目標にしていたOSSへの貢献。 先日初めてとあるOSSにPull Requestを送ってMergeされたのが嬉しかったので、記念に書いておこうと思った。
リリースされたばっかりだし、多分使ってる人もまだ全然いないような小さなプラグインだけど、海外の人と英語でやりとりして自分が書いた修正を取り入れてもらった体験がとても嬉しかったし、かなり勉強になった。
実際のIssueとPRはこちら
今、個人プロジェクト(一応業務でも使う予定)として開発しているAzure Functions上で動くLet's encryptの自動化ツールで、 Certbotを使って証明書の取得や更新を行い、それをAzure Key Vaultにインポートするところまでを自動でやってくれるようなツール。
構成はこんな感じ

CertbotからAzure DNSに対して DNS01 challengeを行ってくれるAzureDNS Certbot pluginを使うことになったのだが、 内部で使われているazure-mgmt-dns · PyPI のバージョンが上がったことによる互換性が崩れてしまう問題でプラグインが上手く動かない現象が出たので、それを修正した感じ。
僕は英語があまり得意ではないので、Isuueで報告する際に、とりあえず伝えたい内容をGoogle翻訳とDeepLの両方を使って翻訳したりして、なんとか作者とやりとりができた。(まじで英語頑張らないと....)
作者の方も割とレンスポンスが早く、素早く僕が送ったPRをチェックしてくれて、次の日にはMergeされてた。
僕の中でのロールモデルにさせて頂いているエンジニアの一人であるsongmuさんのJuly Tech Festaでの発表にとても感銘を受けるものがあり、今後もさらに継続してOSS活動していきたいと思った。
あとは、英語も頑張る。
AWS CLIっぽくAzure Blob StorageのBlobを操作できるCLIツールを作った
普段、業務でサーバーレスアーキテクチャを用いた開発が結構多く、Azure Blob Storageにファイルや画像がアップロードされたら、それをトリガーにAzure Functionsを起動させて処理するみたいな構成が度々あり、ファイル等を手元から手軽アップロードできるように、Azure Blob StorageのBlobをunixコマンドっぽく操作できるCLIツールをPythonで作ってみた。
AWS CLIのaws cli s3コマンドみたいに使えれば良いなと思い、使用感はこちらを参考にさせてもらった。
AWS CLI での高レベル (S3) コマンドの使用 - AWS Command Line Interface
ソースコード
ソースコードはこちら
今回Pythonで作るにあたって、コマンドラインパーサーにclickを使った。
インストール
PyPIからインストールする
$ pip install blobcli
環境変数に操作するBlob Storageの接続文字列を格納する。
$ export AZURE_STORAGE_CONNECTION_STRING="<接続文字列>"
blobcliコマンドで使用可能
$ blobcli Usage: blobcli [OPTIONS] COMMAND [ARGS]... blob storage easy operation cli (v0.0.1) Options: --help Show this message and exit. Commands: cp Copy blob. ls List containers or blobs. mv Move blob. rm Delete blob.
コンテナ、Blobの一覧を表示する
lsオプションでコンテナの一覧を表示
$ blobcli ls
2021-04-04 12:41:09+00:00 samplecontainer01
2021-04-04 12:41:19+00:00 samplecontainer02
2021-04-04 12:41:26+00:00 samplecontainer03
コンテナを指定するとBlobの一覧を表示できる。
$ blobcli ls samplecontainer01
PRE sample-dir01/
PRE sample-dir02/
2021-04-04 12:43:49+00:00 0B sample01.txt
2021-04-04 12:43:54+00:00 0B sample02.txt
2021-04-04 12:43:58+00:00 0B sample03.txt
Blobのコピー、移動
cp,mvオプションを付けてunixコマンドっぽくファイルの移動、コピーができる。
Blob Storage側を指定する場合は以下のようにblob://を付け加える。
blob://<コンテナ名>/<Blob名>
ローカルからBlob StorageへBlobをコピー
$ blobcli cp sample05.txt blob://samplecontainer01/sample-dir01/ copy: sample05.txt to blob://samplecontainer01/sample-dir01/
Blob StorageからローカルへBlobを移動
$ blobcli mv blob://samplecontainer01/sample-dir01/sample05.txt .
move: blob://samplecontainer01/sample-dir01/sample05.txt to .
Blobの削除
rmオプションを付けて、こちらもunixコマンドっぽくファイルの削除ができる。
$ blobcli rm blob://samplecontainer01/sample03.txt
delete: blob://samplecontainer01/sample03.txt
まだできないこと
AWS CLIではできるが、まだblobcliでは対応できてない機能がいくつかある。
感想
普段業務で開発中に毎回Azure Portalからファイルをアップロードして検証していたりしていてなかなか面倒だったので、 今回CLIツールを作ってみてかなり楽になった!
もっと普段の業務などを楽にできるような便利ツールを作っていこうと思う。
Github Actionsの自作アクションを作ってみた
業務でAzure Filesに対してgithubのリポジトリ内容を継続的に反映させる場面があり、いい感じのGithub Actionsあるだろうと思っていたら意外になかったので、この際だから作ってみた。
ソースコードはこちら
Azure Files用のJavaScript SDKを使ってごにょごにょすればいけるかなー思っていたところ、Github ActionsはDockerコンテナ使ったアクションも作れるようなので、Azure CLIを使えば簡単にいけるなと思い、今回はDockerコンテナを使った自作アクションを構築した。
ただ、Dockerコンテナだと、Linux環境でしか動かなかったり(MacOS、Windowsでは動かない)、アクションを実行す際にコンテナのビルドおよび取得のレイテンシにより、速度が遅くなるなどいろいろ制限がある。
ディレクトリ構成
今回作成したアクションのディレクトリ構成はこんな感じ
├── .github
│ └── workflows
│ └── test.yml
├── Dockerfile
├── LICENSE
├── README.md
├── action.yml
└── entrypoint.sh
Dockerfileの作成
アクションの中でAzure CLIを実行させたいので、初めからAzure CLIがインストールされているMicrosoftの公式イメージをベースイメージとする。
FROM mcr.microsoft.com/azure-cli COPY entrypoint.sh /entrypoint.sh RUN chmod +x entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]
アクションのメタデータファイルの作成
こちらを参考にし、アクションの入力、出力、エントリポイントなどを定義していく。
name: 'Azure Files Upload' description: 'This action uses the Azure CLI to upload the selected directory to Azure Files.' auther: 'rnakamine' branding: icon: 'upload-cloud' color: 'blue' inputs: connection_string: description: 'The connection string for the storage account.' required: true source: description: 'The directory to upload files from.' required: true destination: description: 'The destination of the upload operation.' required: true extra_args: description: 'Extra arguments. Can passing flags like `--pattern` or `--destination-path`' required: false runs: using: 'docker' image: 'Dockerfile'
name
アクションの名前
description
アクションの短い説明
auther
アクションの作者の名前
branding
アクションをパーソナライズして見分けられるようにするため、カラーとアイコンを指定してバッジを作成する。 カラーとアイコンはこちらから指定できる。(自分で画像もアップロードできたはず。。)
今回指定したのはこんな感じ

inputs
アクション実行時に使用するデータを指定できる。GithubはinputsパラメータをINPUT_<パラメータ名の大文字>のような形で環境変数として保存する。
runs
Dockerアクションの場合は実行時に使用するイメージを設定する。 Dockerfileを使用する場合
runs: using: 'docker' image: 'Dockerfile'
パブリックなDockerレジストリから使用する場合
runs: using: 'docker' image: 'docker://debian:stretch-slim'
エントリポイントの作成
inputsで受け取ったパラメータを使用し、Azure CLIのaz storage fileコマンドを使用してAzure Filesに対して指定したディレクトリへファイルをアップロードさせている。
#!/bin/sh set -e if [ -z "$INPUT_CONNECTION_STRING" ]; then echo "connection_string is not set. Quitting." exit 1 fi if [ -z "$INPUT_SOURCE" ]; then echo "source is not set. Quitting." exit 1 fi if [ -z "$INPUT_DESTINATION" ]; then echo "destination is not set. Quitting." exit 1 fi EXTRA_ARGS=${INPUT_EXTRA_ARGS:""} az storage file upload-batch --connection-string $INPUT_CONNECTION_STRING --source $INPUT_SOURCE --destination $INPUT_DESTINATION $EXTRA_ARGS
extra_argsでさらに引数を追加できるようにしているので、inputsに無いaz storage fileコマンドのオプションはこちらで指定できる。
動作確認
usesで./と指定して動作確認ができる。今回は以下のようにjobを作って動作確認した。
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ./ with: connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }} source: . destination: test-share extra_args: "--dryrun"
あとは、リリースしてGithub ActionsのMarketplaceに登録しれば完了。リリースするまでの手順に関してはまた別のタイミングで記事を書いていければと思う。
感想
割とハードルが高いと思っていたが、意外とすんなり作ることができた。 今回作ったやつはそんなに大したことしていないが、自作アクションの作成までの流れを知れてよかった。 時間があれば、JavaScriptで作ってマルチプラットフォームで対応できるようにしたい。
