Dockerfile 記述不要の Cloud Native Buildpacks を使ってみる

アプリがコンテナイメージで配布されていると利用者側は導入が楽で便利です。しかし Dockerfile を記述するのはそれなりに経験が必要で、試行錯誤してるうちかなりの時間が溶けてることもままあります。Dockerfile 書いた後もセキュリティスキャンとか、ベースイメージ更新など色々とやることがあります。

Cloud Native Buildpacks は、プログラミング言語・フレームワーク・ビルドツールなどのアプリ構成に応じてコンテナイメージをよしなに作ってくれるツールです。元々 Heloku で開発されて今は CNCF*1 のプロジェクトになっています。

buildpacks.io

ドキュメントにしたがって macOS にインストール。

Installing `pack` · Cloud Native Buildpack Documentation

$ brew tap buildpack/tap
$ brew install pack

サンプルアプリの java-maven をビルドしてみます。

$ git clone https://github.com/buildpacks/samples
$ pack build sample-app --path samples/apps/java-maven --builder cnbs/sample-builder:bionic

SpringBoot アプリのようで、 maven のビルドが延々と続きます。

Successfully built image sample-app

と表示されると成功です。Dockerfile は1行も書いていません。

ビルドされたコンテナを実行。

f:id:kondoumh:20200525235151p:plain

localhost:8080 で起動されたアプリにアクセスできます。

f:id:kondoumh:20200525234247p:plain

コンテナイメージを表示してみるとちゃんと sample-app のイメージがビルドされています*2。 ビルドに必要なイメージも付随してビルドされている模様です。

docker image ls           

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
cnbs/sample-stack-run   bionic              660461c452f1        3 weeks ago         71.2MB
cnbs/sample-builder     bionic              bcb0e9518efb        40 years ago        181MB
sample-app              latest              6c61aaf82fc8        40 years ago        301MB

プロジェクトの POM ファイルを解析してよしなにコンテナイメージをビルドしてくれました。SpringBoot アプリではもう自前で Dockerfile 書かなくてもよくなってるんですね。これはなかなかすごいです。

サンプルには、この java-maven の他に kotlin-gradle や ruby-bundler が含まれていていずれも同じようにコンテナを build / run できました。

Document によると Buildpack というモジュールにより、アプリのコードや構成を調査してフレームワークやビルド手順を検出しビルドする模様です。

Buildpack · Cloud Native Buildpack Documentation

このリポジトリでは、Java / .NET Core / Node.js / Go / PHP の Buildpack が公開されています。

github.com

GitHub から Node.js の サンプルを取ってきて Node.js の Buildpack を指定してイメージをビルドしてみます。

github.com

$ git clone git@github.com:contentful/the-example-app.nodejs.git
$ cd the-example-app.nodejs
$ pack build example-app-node:latest --builder gcr.io/paketo-buildpacks/builder:base
:
Successfully built image exapmple-app-node:latest

ビルド成功しました。docker run で実行してみます。

$ docker run --rm -p 3000:3000 example-app-node

> example-contentful-theExampleApp-js@0.0.0 start /workspace
> NODE_ENV=production node ./bin/www

Listening on http://localhost:3000

無事起動しました。

Vue.js のアプリは依存関係のライブラリのビルドに Python が必要だったりしてこの Buildpack ではビルドできませんでしたが、今後は Dockerfile 書かなくてもビルド・デプロイできるアプリが増えていきそうです。

Buildpacks にはベースイメージの rebase など運用上便利そうな機能もあり期待が持てます。

Rebase · Cloud Native Buildpack Documentation

*1:Cloud Native Comupting Foundation https://www.cncf.io/

*2:40年前に作られたという謎の状態ですが。

setup-terraform Action で Terraform 実行を簡潔に

HashiCorp から GitHub Actions の setup-terraform Action が登場しました。

github.com

公式ドキュメントはこちら

www.terraform.io

従来は terraform-github-actions が提供されていましたが、こちらは今凍結されています。

github.com

Terraform は init / validate / plan / apply と複数のサブコマンドを実行する必要がありますが、terraform-github-actions は Dockerfile Action のため繰り返し uses: で Action を実行する必要がありました。

setup-terraform は JavaScript Action であり、ワークフロー実行環境に terraform CLI を文字通りセットアップしてくれるので、run: でサブコマンドを自由に実行できます。

blog.kondoumh.com

以前 DigitalOcean の Droplets を作るワークフローを terraform-github-actions で作りました。

blog.kondoumh.com

Action を繰り返し適用しているためかなり冗長でした。setup-terraform に置き換えてみます。

PR 作成時のワークフロー

name: Terraform Plan for droplet

on:
  pull_request:
    types: [opened]

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TF_TOKEN }}
  DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout'
        uses: actions/checkout@v2
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: 0.12.24
      - name: 'Terraform Init'
        run: terraform init
      - name: 'Terraform Validate'
        run: terraform validate
      - name: 'Terraform Plan'
        run: terraform plan

PR マージ時のワークフロー

name: Terraform Apply droplet

on:
  pull_request:
    types: [closed]

env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TF_TOKEN }}
  DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout'
        uses: actions/checkout@v2
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: 0.12.24
      - name: 'Terraform Init'
        run: terraform init
      - name: 'Terraform Apply'
        run: terraform apply -auto-approve
        if: github.event.pull_request.merged == true

かなり簡潔に書けるようになりました。

ノート・メモ環境 2020 (だいたい Scrapbox)

ノート・メモやタスク管理については過去にもちょいちょい書いてました。

blog.kondoumh.com

blog.kondoumh.com

blog.kondoumh.com

最近はやはり Scrapbox 中心になっています。

書きはじめ

作業計画や実装方式を検討する時などまとまったテキストを書く時、Scrapbox で scratchpad 的なページを作っておいてとにかく素早く書き始めるというのをやってます。Scrapbox は保存について考えなくてもよいので。よくやるテキストエディタの新規バッファで書くのは保存せずに終了させてしまった時のダメージが大きいし、ファイル名を付けるのはある程度内容が明確になるまではペンディングしたいというのがあります。

Markdown で書きたい場合、テキストエディタでは拡張子 .md などのファイルとして保存しないとシンタックスハイライトが効きません。Scrapbox のコードブロックを使えば code:hoge.md とか書くだけでシンタックスハイライト付きのテキストエリアを使えます。ある程度書いたら VS Code や Wiki に持っていきます。

Slack の個人チャンネルで書いてたこともあったのですが Markdown サポートが微妙になったのでやめました。

TODO 管理

ステイホームの影響で買い物を効率的にしないといけないので Google Keep に買うものリストを作るようになりました。Web サイトでも編集できて日常のタスク管理には充分そうです。

chrome.google.com

Trello はほとんど使わなくなり、役所手続きなど年単位の定期的なタスクの管理だけ残ってます。

blog.kondoumh.com

Scrapbox でも TODO 管理を試みたことがありますがちょっとなじみませんでした。

GitHub の Project board も開発にまつわるタスク管理には使っています。

プロジェクトボードについて - GitHub ヘルプ

作業ログ

Emacs で ChangeLog メモ書いてます (14年目!)。

blog.kondoumh.com

改行はしない縛りで80文字前後で箇条書きするようにしてます。「内容を想起できるギリギリの短いセンテンスを書く」という謎の能力が鍛えられるような・・。

文書作成

構造化文書も Scrapbox で書くことが増えました*1。Scrapbox -> Markdown 変換ツールを書いたのでエクスポートも簡単です。

blog.kondoumh.com

blog.kondoumh.com

Markdown 変換後は VS Code のプラグインで PDF に変換することも多いです。

marketplace.visualstudio.com

コードスニペット

これも Scrapbox のコードブロックに書いてます。Gist は全然使ってませんでしたが、使って行こうかなと思ってます。

まとめ

Scrapbox のカバレッジが広い。ステイホームもあってモバイルで何かを書くということも少なくなってますが、Scrapbox がモバイルでも使い勝手よくなってるのであまり変わらないかも。

*1:kibela もちょっと使ってたのですが動作が安定しないのとプレビューが面倒で使わなくなりました。

Netlify への JSON publish を GitHub Actions に移行

以前 Tumblr の JSON データを Netlify で定期的に配信するようにしました。

blog.kondoumh.com

その時は GCP の Cloud Scheduler を使って定期的に GitHub repo の Master branch を取得しデプロイ時に Netlify build を使って JSON ファイルを生成していました。Netlify にはスケジューラ機能がないので外部から Webhook で build をトリガーする必要があったのです。

f:id:kondoumh:20200504201440p:plain

この CI を GitHub Actions に移行しました。GCP の無料トライアル期間がもうすぐ切れるのと、GitHub Actions の方が NPM Script を実行するだけでなく色々できるからです。スケジューラも自前で持っていますし。

GitHub の Marketplace に Netlify API を使って Deploy をやってくれる Action がありました。

github.com

Netlify API を使うため対象サイトの settings > general で API ID を取得します。

f:id:kondoumh:20200504100957p:plain

Netlify へのアクセストークンを Netlify App のページで発行します。

Netlify App

各トークンや API ID をリポジトリの Secrets に登録しておきます。スケジュール起動で 9時と21時*1に Tumblr から JSON データを取得し Netlify サイトにデプロイする Action はこんな風に書けます。

name: Build and publish

on:
  schedule:
    - cron:  '0 9,21 * * *'

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js 12.x
      uses: actions/setup-node@v1
      with:
        node-version: 12.x
    - name: Fetch posts data from tumblr
      run: |
        npm install
        npm run env
      env:
        BLOG_IDENTIFIER: ${{ secrets.BLOG_IDENTIFIER }}
        BLOG_API_KEY: ${{ secrets.BLOG_API_KEY }}

    - name: Deploy to netlify
      uses: nwtgck/actions-netlify@v1.0
      with:
        publish-dir: './public'
        production-branch: master
        github-token: ${{ secrets.GITHUB_TOKEN }}
        deploy-message: "Deploy from GitHub Actions"
      env:
        NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
        NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

以前は、Slack に通知してましたが、actions-netlify が GItHub に通知してくれるし、通知もさほど重要じゃないのでやめました。

構成はこんな感じになりました。

f:id:kondoumh:20200504201558p:plain

Netlify の Edge サービスとしての利便性を享受しつつ GitHub Actions のリッチなパイプラインが使えるというのは悪くない感じします。

*1:UTC なので Asia/Tokyo TZ では 18時と6時ですが。

Scrapbox のページを Markdown に変換する CLI

以前、野良 Scrapbox アプリでページ本文を Markdown にしてクリップボードにコピーする機能を実装しました。

blog.kondoumh.com

アプリを使っていなくて変換だけを使いたい場合もあるかもということで、CLI を作りました。Scrapbox API でページの JSON を取ってきて Markdown に変換して標準出力に出力するだけです。

github.com

Go の regexp の使い方が学べました。

GCP の Microservices demo を GKE で動かしてみる

今年の初めに Google Cloud からマイクロサービスアーキテクチャのデモアプリを GCP にデプロイするハンズオンが出てました。

cloud.google.com

Hipster Shop という EC サイトのデモアプリケーションを fork して使用します。

github.com

ベースは2年ぐらい前のコードですが、メンテナンスされ続けている模様です。以下のマイクロサービスから構成されています。

micro service description
adservice Java で書かれた アドサービス
cartservice C# で書かれたカートサービス
checkoutservice Go で書かれたチェックアウトサービス
currencyservice Node.js で書かれた通貨換算サービス
emailservice Python で書かれたメールサービス
frontend Go で書かれた Web フロントエンド
paymentservice Node.js で書かれた支払いサービス
productcatalogservice Go で書かれた商品カタログサービス
recommendationservice Python で書かれたレコメンドサービス
shippingservice Go で書かれた出荷サービス

このように Multi language な MonoRepo になっています。やっぱ Go が多い。なお、loadgenerator という Python のサブプロジェクトがありますが、これは LOCUST を使った負荷テスト (ユーザの買い物に伴うリクエストを投げ続ける) のようです。

Locust - A modern load testing framework

Frontend を中心に各マイクロサービスと連携することで、買い物のサービスを提供します。マイクロサービス間の通信は gRPC で行います。

https://github.com/GoogleCloudPlatform/microservices-demo/raw/master/docs/img/architecture-diagram.png

https://github.com/GoogleCloudPlatform/microservices-demo/raw/master/docs/img/architecture-diagram.png
README の Architecture 図

各マイクロサービスは Kubernetes クラスターに Skaffold によりデプロイできる manifest が用意されています*1

GitHub Actions による CI も作られています。

microservices-demo/ci.yml at master · GoogleCloudPlatform/microservices-demo · GitHub

CI を実行するには GCP の VM に Self-hosted runner を立てて実行する必要があります。

microservices-demo/README.md at master · GoogleCloudPlatform/microservices-demo · GitHub

Kubernetes クラスターとしては kind を使って LOCUST を使ったテストを流しています。

github.com

以下ハンズオンにしたがってデプロイしていきます。

必要な GCP のサービス有効化は数分かかりました。

$ gcloud services enable cloudbuild.googleapis.com sourcerepo.googleapis.com containerregis
try.googleapis.com container.googleapis.com cloudtrace.googleapis.com cloudprofiler.googleapis.com logging.googleapis.com compute.g
oogleapis.com
Operation "operations/xxxxxxxxxxxxxxxxx" finished successfully.

ワーカーノード max 10 の GKE クラスターを作成。これは5分ぐらいで起動。作成時はノード数は5でした。

$ gcloud container clusters create demo --enable-autoupgrade --enable-autoscaling --min-nodes=3 --max-nodes=10 --num-nodes=5 --zone=asia-northeast1-a

:
Creating cluster demo in asia-northeast1-a... Cluster is being health-checked (master is healthy)...done.
kubeconfig entry generated for demo.
:

$ kubectl get nodes
NAME                                  STATUS   ROLES    AGE   VERSION
gke-demo-default-pool-c6ce71ca-3185   Ready    <none>   30s   v1.14.10-gke.27
gke-demo-default-pool-c6ce71ca-d7h0   Ready    <none>   30s   v1.14.10-gke.27
gke-demo-default-pool-c6ce71ca-j1t7   Ready    <none>   28s   v1.14.10-gke.27
gke-demo-default-pool-c6ce71ca-rdxq   Ready    <none>   27s   v1.14.10-gke.27
gke-demo-default-pool-c6ce71ca-sxgl   Ready    <none>   29s   v1.14.10-gke.27

skaffold を使ってアプリをデプロイ。Java や Node.js や Go のプログラムのビルドとコンテナイメージ作成をしているのでフルビルドだとそれなりに待ちます*2

$ skaffold run -p gcb --default-repo=gcr.io/<YOUR PROJECT ID>

Pod や Service は default namespace にデプロイされました。

$ kubectl get po,svc
NAME                                         READY   STATUS    RESTARTS   AGE
pod/adservice-649b679899-x9b8d               1/1     Running   0          3m52s
pod/cartservice-5c8bcb96b-5pwx7              1/1     Running   2          3m51s
pod/checkoutservice-777768cbf8-n6t8v         1/1     Running   0          3m51s
pod/currencyservice-85dc68bfbb-2rwzt         1/1     Running   0          3m50s
pod/emailservice-5d79bd9fd6-tjpt7            1/1     Running   0          3m50s
pod/frontend-74f6c656df-hzfmw                1/1     Running   0          3m50s
pod/loadgenerator-7cb8bdb989-vsb46           1/1     Running   4          3m49s
pod/paymentservice-795fb7654b-p576r          1/1     Running   0          3m49s
pod/productcatalogservice-57466c6bd7-jwrbp   1/1     Running   0          3m49s
pod/recommendationservice-649f74ff78-6tp5m   1/1     Running   0          3m49s
pod/redis-cart-7b6f64b5cd-p79nc              1/1     Running   0          3m48s
pod/shippingservice-6ff68f9b69-4jtxh         1/1     Running   0          3m48s
NAME                            TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
service/adservice               ClusterIP      10.7.247.68    <none>          9555/TCP       3m51s
service/cartservice             ClusterIP      10.7.252.157   <none>          7070/TCP       3m51s
service/checkoutservice         ClusterIP      10.7.247.183   <none>          5050/TCP       3m51s
service/currencyservice         ClusterIP      10.7.251.225   <none>          7000/TCP       3m50s
service/emailservice            ClusterIP      10.7.246.34    <none>          5000/TCP       3m50s
service/frontend                ClusterIP      10.7.251.19    <none>          80/TCP         3m50s
service/frontend-external       LoadBalancer   10.7.241.65    35.xxx.xxx.xx   80:32118/TCP   3m50s
service/kubernetes              ClusterIP      10.7.240.1     <none>          443/TCP        14m
service/paymentservice          ClusterIP      10.7.252.240   <none>          50051/TCP      3m49s
service/productcatalogservice   ClusterIP      10.7.253.164   <none>          3550/TCP       3m49s
service/recommendationservice   ClusterIP      10.7.254.110   <none>          8080/TCP       3m49s
service/redis-cart              ClusterIP      10.7.253.45    <none>          6379/TCP       3m48s
service/shippingservice         ClusterIP      10.7.252.132   <none>          50051/TCP      3m48s

frontend-external の EXTERNAL-IP にアクセスしたらアプリに接続できました。

f:id:kondoumh:20200430162730p:plain

Container Registry を確認すると push 済みの image が並んでいます。

f:id:kondoumh:20200430163255p:plain

カートに商品を入れてみて通貨換算が機能しているのも確認できます。

f:id:kondoumh:20200430164153p:plain

f:id:kondoumh:20200430164210p:plain

この後、コードの修正とビルド・再デプロイや Kubernetes の機能を活かしたオートヒーリングやオートスケーリングの動作を確認するというハンズオンが続きます。

商品をカートに入れてオーダーして出荷という一連の処理をした後にログを確認してみました。

f:id:kondoumh:20200430170745p:plain

Kubernetes のログもちゃんと Web UI で取れますね。kubectl logs <Pod name> でも取れますが、大量になるのでプラットフォームにインテグレートされているのはいいことです。

以上、Microservices デモを GKE にデプロイして動かしてみました。frontend の Go は 1.12 とちょっと古いですが、マイクロサービスなのでマイグレーションも楽そうです。gRPC のサンプルとしても良さそうですね。

Argo CD を導入して GitOps の素振りなどにも使えそうです。

blog.kondoumh.com

*1:デプロイについては、kustomize の方が主流になっていく気がしますが。

*2:ハンズオンには30分かかると書いてありますが、そこまではかかりませんでした。

Scrapbox ページ間リンクを可視化するための CLI をリリース

以前 Scrapbox のページ間リンクを Graphviz で可視化してみました。

blog.kondoumh.com

Scrapbox からデータを取得し、可視化用の dot ファイルを生成するところまでをカバーした CLI ツールを mamezou-tech で公開しました。

github.com

README と help を見ればおおよその使用方法はわかると思います。

$ sbgraph init
$ sbgraph project kondoumh
$ sbgraph fetch
$ sbgraph graph

このステップで dot ファイルが生成されます。graph サブコマンドには大量データをフィルタするためのオプションもあります。

あとは、dot コマンドで、グラフの画像を生成すれば Scrpbox プロジェクトのグラフ構造を見ることができます。