kustomize buildを実行して得たmanifestの差分ををPull Request上で確認する

この記事は GMOペパボエンジニアカレンダー の19日目の記事です。諸事情によりバタバタしてしまい、記事の公開がめちゃくちゃ遅くなりました🙏

adventar.org


普段運用しているKubernetesクラスターでは、デプロイするmanifestは環境毎の差分を管理しやすくするために kustomize を用いています。 共通のmanifestをベースに環境毎にpatchを当てたりすることが多いのですが、これが複雑になってくると、少しの変更でも影響範囲を把握するのが大変だったりするので、毎回手元でkustomize buildを実行して差分を確認するようにしていました。

例えば私の場合だと、

  1. 現在のブランチでkustomize buildを実行して得たmanifestを何かしらのファイルに書き込んでおく。
  2. 指定したブランチ(mainブランチとか)に切り替えて、再度kustomize buildを実行し、1 との差分をdiffコマンド等で確認する。

といったようなことを行なっていました。(もっと良い方法があるかもしれないですが...)

これをPull Request上で確認できるとめちゃくちゃ嬉しいなと思い、何か良い感じのツールが無いか探していたところ git-kustomize-diff というツールがかなり便利そうだったので、今回はGithub ActionsでPull Requestをトリガーに、kustomize buildを実行して得たmanifestの差分をPull Request上で確認できるようにする方法をご紹介します。

github.com

こんな感じでブランチ間の差分を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 を使用します。

github.com

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の使い方はこちらを参考にさせてもらいました。

k1low.hatenablog.com

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イベントだと取得できないはず...)

zenn.dev

感想

git-kustomize-diff を使うことで、環境毎・リソース毎にブランチ間の差分を一発で見れるようになるので、自身での差分の確認も楽になるのですが ghput と組み合わせることにより、GithubのPull Reqeust上でこれらの差分が確認できるようになるので、コードレビューのコストもかなり下げられるのではないかと思いました!

istio-proxy(envoy) のアクセスログをパースして任意の形式で出力するツールを作った

この記事は CODE BASE OKINAWA Advent Calendar 2022 - Adventar の12日目の記事です。

adventar.org

今日は、普段のお仕事を少しだけ楽にするツールを書いたので、紹介していきたいと思います。


普段運用している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 というツールを作成しました。

github.com

使い方

ログを標準入力から受け取り、任意の形式で出力することができます。

echo '<Istio Proxy Access Log>' | istio-axslog [flags]

現在サポートされている出力形式は、jsonltsvです。

--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
...

もちろん、jqlltsv などの他のツールと組み合わせて使うこともできます。

仕組み

仕組みはめちゃくちゃシンプルで、ログを標準入力から受け取り、1行ずつ正規表現を用いてパースして、 構造体 を生成しています。ログはこちらの Default access log format を元にパースしています。

構造体にしてしまえば、あとは標準パッケージや外部のパッケージなんかを使って構造体を簡単にjsonなりltsvの形式に変換することができるので、それらを繰り返し標準出力に出すだけです。めちゃくちゃシンプル。

最後に

インストールもmacOSならhomebrewからインストールできるようにしており、他の環境でも こちら からバイナリを落としてくるだけで使えるようになっています。

冒頭でも挙げたように、istio-proxyのアクセスログがパッと分かりづらいと思っている方がいらっしゃいましたら、是非使ってみてください!(もちろんフィードバックやPRもお待ちしております)

github.com

Goを用いてTCPソケット通信を学ぶ

この記事は GMOペパボエンジニア Advent Calendar 2021 - Adventar の6日目の記事です。

昨日は buty4649 さんの業務で使っているPCをLinuxデスクトップにしてから3年半が経った でした。 これまでデスクトップPCはWindowsMacしか使ったことがなかったので、Linuxデスクトップの話は個人的にはかなり新鮮な内容でした。その他にも普段開発で利用しているアプリケーションの紹介もされていて、最近リモートで働く中でなかなか自分以外の開発環境を見る機会がほとんど無いので、めちゃくちゃ参考になりました!

はじめに

以前同僚がこんな記事を書いてて

ryuichi1208.hateblo.jp

ん....そもそもソケット自体よく分かっていない....どうしよう....って状態だったので、このタイミングで改めて学習し直しました。 今回はTCPソケット通信の処理の流れだったり、その中で使われているシステムコールについて見ていきながら、最後にGoを用いて簡単なTCPサーバ / クライアントを動かしてみようと思います。

TCPソケットとは

TCP/IP アプリケーションを作成するための抽象化されたインターフェースのことで、ユーザープロセスとTCP/IPを繋ぐ出入り口になる。 お互いのソケットをネットワークを通して接続し,その口を通して通信を実現することができます。 ソケットを利用することで、TCPの処理はOS側で行ってくれることによって、アプリケーション側では意識しなくてもよくなります。


ソケット自体は以下の2種類があります。

※訂正: 下記の2つを含め他にもたくさんの種類のソケットがあるそうです。( Man page of SOCKET ) ご指摘頂きありがとうございます!

  • INETドメイン(ネットワークソケット)

  • UNIXドメイン

    • 同じマシン内でプロセス間の通信を行うためのソケット。
    • ソケットファイルを利用する (例: /run/php-fpm/php-fpm.sock)
    • INETドメインソケットよりも高速

今回はINETドメインに着目してTCP/IPにおけるソケット通信の流れを見ていきます。

ソケット通信の基本となる流れは以下のようになっています。 その中でいくつかのシステムコールを呼び出して通信を行なっています。

f:id:rnakamine:20211206173510p:plain

socket()

ソケットを作成する。(サーバ/クライアント)

Man page of SOCKET

bind()

作成したソケットに対してポートをなどを割り当てる。(サーバ)

Man page of BIND

listen()

ソケットを接続待ちの状態にする。(サーバ)

Man page of LISTEN

accept()

クライアントから来た接続要求に対してソケットへの接続を受ける。(サーバ)

Man page of ACCEPT

connect()

サーバに対してソケット接続を要求する。(クライアント)

Man page of CONNECT

read() / write()

クライアント or サーバから来たデータの受信と送信を行う。(サーバ/クライアント)

Man page of READ Man page of WRITE

close()

接続を切断する。(サーバ/クライアント)

Man page of 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()メソッドを呼び出すことで、クライアントからの接続を待ち受けることができます。

実際にどんなシステムコールがどのような順番で呼び出されているかは、こちらを参考にさせて頂きました。

ks888.hatenablog.com

クライアント側

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入門
  • 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の様々な活用方法
    • 10.1 チーム開発で開発環境を統一・共有する
    • 10.2 コマンドラインツール(CLI)をDockerコンテナで利用する
    • 10.3 負荷テスト
  • 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

    • クラスタの管理下に登録されているDockerホスト。コンテナをデプロイするために利用される
    • クラスタはMaster Node、Node群によって構成される
  • Namespace

  • Pod

    • コンテナの集合体
    • 少なくとも1つのコンテナを持つ
  • ReplicaSet

    • 同じ仕様のPodを複数生成・管理するためのリソース
  • Deployment

    • アプリケーションデプロイの基本単位となるリソース
    • ReplicaSetを管理・操作するためのリソース
  • Service

    • Podの集合に対する経路やサービスディスカバリを提供するためのリソース
      • サービスディスカバリ
        • クライアントから一貫した名前でアクセスできるようにする仕組み
  • Ingress

    • ServiceをHTTP/HTTPSベースでクラスタ外に公開できるようにするためのリソース
  • Job

    • 1つ以上のPodを作成し、指定された数のPodが正常に完了するまでを管理するリソース
    • Webアプリケーションなどの常駐型のアプリケーションではなく、大規模な計算やバッチ処理などに使われる
  • Secret

  • DaemonSet

    • クラスタで管理されている全てのNodeに対して必ず1つ配置されるPodを管理するためのリソース
    • ログ収集用のエージェントなどを配置したいときに使われる

デプロイについて

RollingUpdate

古いバージョンのアプリケーションを実行した状態で新しいアプリケーションを起動し、準備が完了したものから徐々にサービスインしていく仕組み。

BlueGreen Deployment

新旧2つのバージョンのサーバ群(ここではコンテナ)を一気に切り替えてデプロイする手法。ロールバックも容易。

感想

Kubernetesの概要や、各リソース・コンポーネント、またはそれらの役割について、全てではないがある程度ざっくり押さえることができたと思う。 この本はKubernetes以外にも、Dockerの基礎やDocker Swarm、Dockerのセキュリティや開発を支援するツールなどにも触れていて、結構ボリュームがある内容だった。

他にもいくつか書籍を当たってみたあと、実際に手を動かして慣れていきたい。

OSSに初めてコントリビュートできて嬉しかった話

今年目標にしていたOSSへの貢献。 先日初めてとあるOSSにPull Requestを送ってMergeされたのが嬉しかったので、記念に書いておこうと思った。

リリースされたばっかりだし、多分使ってる人もまだ全然いないような小さなプラグインだけど、海外の人と英語でやりとりして自分が書いた修正を取り入れてもらった体験がとても嬉しかったし、かなり勉強になった。

実際のIssueとPRはこちら

github.com

github.com

今、個人プロジェクト(一応業務でも使う予定)として開発しているAzure Functions上で動くLet's encryptの自動化ツールで、 Certbotを使って証明書の取得や更新を行い、それをAzure Key Vaultにインポートするところまでを自動でやってくれるようなツール。

github.com

構成はこんな感じ

f:id:rnakamine:20210422105859p:plain

CertbotからAzure DNSに対して DNS01 challengeを行ってくれるAzureDNS Certbot pluginを使うことになったのだが、 内部で使われているazure-mgmt-dns · PyPI のバージョンが上がったことによる互換性が崩れてしまう問題でプラグインが上手く動かない現象が出たので、それを修正した感じ。

僕は英語があまり得意ではないので、Isuueで報告する際に、とりあえず伝えたい内容をGoogle翻訳とDeepLの両方を使って翻訳したりして、なんとか作者とやりとりができた。(まじで英語頑張らないと....)

作者の方も割とレンスポンスが早く、素早く僕が送ったPRをチェックしてくれて、次の日にはMergeされてた。

僕の中でのロールモデルにさせて頂いているエンジニアの一人であるsongmuさんのJuly Tech Festaでの発表にとても感銘を受けるものがあり、今後もさらに継続してOSS活動していきたいと思った。

youtu.be

あとは、英語も頑張る。

AWS CLIっぽくAzure Blob StorageのBlobを操作できるCLIツールを作った

普段、業務でサーバーレスアーキテクチャを用いた開発が結構多く、Azure Blob Storageにファイルや画像がアップロードされたら、それをトリガーにAzure Functionsを起動させて処理するみたいな構成が度々あり、ファイル等を手元から手軽アップロードできるように、Azure Blob StorageのBlobをunixコマンドっぽく操作できるCLIツールをPythonで作ってみた。

AWS CLIaws cli s3コマンドみたいに使えれば良いなと思い、使用感はこちらを参考にさせてもらった。

AWS CLI での高レベル (S3) コマンドの使用 - AWS Command Line Interface

ソースコード

ソースコードはこちら

github.com

今回Pythonで作るにあたって、コマンドラインパーサーにclickを使った。

click.palletsprojects.com

rnakamine.hatenablog.com

インストール

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あるだろうと思っていたら意外になかったので、この際だから作ってみた。

ソースコードはこちら

github.com

Azure Files用のJavaScript SDKを使ってごにょごにょすればいけるかなー思っていたところ、Github ActionsはDockerコンテナ使ったアクションも作れるようなので、Azure CLIを使えば簡単にいけるなと思い、今回はDockerコンテナを使った自作アクションを構築した。

ただ、Dockerコンテナだと、Linux環境でしか動かなかったり(MacOSWindowsでは動かない)、アクションを実行す際にコンテナのビルドおよび取得のレイテンシにより、速度が遅くなるなどいろいろ制限がある。

ディレクトリ構成

今回作成したアクションのディレクトリ構成はこんな感じ

├── .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

アクションをパーソナライズして見分けられるようにするため、カラーとアイコンを指定してバッジを作成する。 カラーとアイコンはこちらから指定できる。(自分で画像もアップロードできたはず。。)

今回指定したのはこんな感じ f:id:rnakamine:20210422072154p:plain

inputs

アクション実行時に使用するデータを指定できる。GithubはinputsパラメータをINPUT_<パラメータ名の大文字>のような形で環境変数として保存する。

runs

Dockerアクションの場合は実行時に使用するイメージを設定する。 Dockerfileを使用する場合

runs:
  using: 'docker'
  image: 'Dockerfile'

パブリックなDockerレジストリから使用する場合

runs:
  using: 'docker'
  image: 'docker://debian:stretch-slim'

エントリポイントの作成

inputsで受け取ったパラメータを使用し、Azure CLIaz 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で作ってマルチプラットフォームで対応できるようにしたい。