Cloud Native Buildpack に入門:Maven プロジェクトをビルド

趣旨

maven wrapper を配置したマルチモジュールな Maven プロジェクトを pack cli でビルドしてみた

  • 指定条件
    • 成果物は 80 MB くらいの war ファイル
    • JDK は 1.8
    • Tomcat は 7.0.96
  • 結果
    • 314 MB くらいのイメージが作成された
    • そんなもんかな、という印象

Cloud Native Buildpack とは

buildpacks.io

アプリケーションコードから自動的にコンテナイメージを作成する仕組み、を標準化した仕様。

Dockerfile を書かなくてもよい、レイヤーを再利用できる、レイヤーを差し替えられるのがメリット。

アプリケーションとコンテナ実行基盤を疎結合にするツールとしてとてもよさそう。 コンテナ実行基盤を PaaS と同じように考えられるので。

やったこと

$ docker version
Client:
 Version:           19.03.1
 API version:       1.39 (downgraded from 1.40)
 Go version:        go1.12.7
 Git commit:        74b1e89e8a
 Built:             Wed Jul 31 15:18:18 2019
 OS/Arch:           windows/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.9
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.11.13
  Git commit:       039a7df9ba
  Built:            Wed Sep  4 16:55:50 2019
  OS/Arch:          linux/amd64
  Experimental:     false

$ pack build myapp --builder cloudfoundry/cnb:bionic --env BP_JAVA_VERSION="8.*" --env BP_BUILT_MODULE="webapp" --env BP_TOMCAT_VERSION="7.*"

$ docker images | grep myapp
myapp   latest   6342132d1392   18 seconds ago   314MB

--builder cloudfoundry/cnb:bionic

$ pack suggest-builders
Suggested builders:
        Cloud Foundry:     cloudfoundry/cnb:bionic         Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang
        Cloud Foundry:     cloudfoundry/cnb:cflinuxfs3     cflinuxfs3 base image with buildpacks for Java, .NET, NodeJS, Python, Golang, PHP, HTTPD and NGINX
        Heroku:            heroku/buildpacks:18            heroku-18 base image with buildpacks for Ruby, Java, Node.js, Python, Golang, & PHP

--env BP_JAVA_VERSION="8.*"

cloudfoundry/openjdk-cnb の動作を制御する環境変数

  • 始めの方に配置されるプランで、Java のバージョンを指定できる。
  • ワイルドカード指定 (8.*) ができる。
  • 指定したバージョンの OpenJDK をダウンロードして配置してくれる。
  • このプランで指定された JAVA_HOME が後続のプランでも使われる。

--env BP_BUILT_MODULE="webapp"

cloudfoundry/build-system-cnb の動作を制御する環境変数

  • ビルドした成果物を探索するパス文字列パターンの先頭に指定される文字列。
  • mvnwgradlew が存在するならそれぞれのラッパースクリプトを使用するのでなんか面白い。
  • BP_BUILD_ARGUMENTS を指定するとラッパースクリプトの引数全体を上書きできる。

--env BP_TOMCAT_VERSION="7.*"

cloudfoundry/tomcat-cnb の動作を制御する環境変数

  • Tomcat のバージョンを指定できる。
  • ワイルドカード指定 (7.*) ができる。
  • 指定したバージョンの Apache Tomcat をダウンロードして配置してくれる。
  • 設定ファイルを追加したい場合は BP_TOMCAT_EXT_CONF_VERSION/BP_TOMCAT_EXT_CONF_URI/BP_TOMCAT_EXT_CONF_SHA256 と3つの環境変数を指定するらしい。
    • 指定する内容はアクセス可能な buildpack の情報らしい。
    • よく分からない。

kind で構成したクラスタから private registry へアクセスする(正常に動作しなかった)

趣旨

  • k8s(kind) から k8s(minikube) にデプロイした Docker Registry へアクセスしてみようとした
  • k8s(kind) の kubelet から利用する containerd自己署名証明書を許容しなかった
  • 設定で回避することもできず諦めた

やったこと

  1. TLS 通信のための自己署名証明書を作る
  2. ベーシック認証のための秘密情報ファイルを作る
  3. registry:2 をデプロイするためのマニフェストを作成する
  4. registry:2 をデプロイする
  5. docker を構成する
  6. kind を構成する
  7. kind でイメージを実行する

1. TLS 通信のための自己署名証明書を作る

外部から参照可能な名前を指定する。

ローカルネットワークだけで完結する場合は xip.io を利用すると便利。 myhost.<private ip address>.xip.io とすると <private ip address> に解決してくれる。

cn="registry.10.0.0.1.xip.io"
openssl req -newkey rsa:2048 -nodes -x509 -days 3650 \
    -subj "/C=JA/ST=Tokyo/L=Shinjuku/CN=${cn}" \
    -keyout ./certs/tls.key \
    -out ./certs/tls.crt

2. ベーシック認証のための秘密情報ファイルを作る

こちらのイメージを利用する。

hub.docker.com

docker run --rm -ti xmartlabs/htpasswd testuser pass01  > ./auth/htpasswd

3. registry:2 をデプロイするためのマニフェストを作成する

次のような注意点がある。

  • 環境変数で置換する部分がある ($cn の部分)
  • Nginx Ingress Controller の設定で proxy-body-size を大きくしてる
## registry.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: registry
  labels:
    app: registry
spec:
  replicas: 1
  selector:
    matchLabels:
      app: registry
  template:
    metadata:
      labels:
        app: registry
    spec:
      containers:
      - name: registry
        image: registry:2
        ports:
        - name: registry
          containerPort: 5000
        volumeMounts:
        - name: auth
          mountPath: "/auth"
        - name: data
          mountPath: "/var/lib/registry"
        env:
        - name: REGISTRY_AUTH_HTPASSWD_REALM
          value: "basic"
        - name: REGISTRY_AUTH_HTPASSWD_PATH
          value: "/auth/htpasswd"
      volumes:
      - name: auth
        secret:
          secretName: registry-auth
      - name: data
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: registry
  labels:
    app: registry
spec:
  selector:
    app: registry
  ports:
  - name: registry
    protocol: TCP
    port: 5000
    targetPort: 5000
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: registry
  annotations:
    kubernetes.io/ingress.class: "nginx"
    ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/proxy-body-size: "999m"
spec:
  tls:
  - hosts:
    - ${cn}
    secretName: registry-tls
  rules:
  - host: ${cn}
    http:
      paths:
      - path: /
        backend:
          serviceName: registry
          servicePort: 5000

4. registry:2 をデプロイする

名前空間を作る。

kubectl create ns registry

Ingress から参照する secret を作る。

kubectl -n registry create secret tls registry-tls --cert=./certs/tls.crt  --key=./certs/tls.key

マニフェストから参照する secret を作る。

kubectl -n registry create secret generic registry-auth --from-file=htpasswd=./auth/htpasswd

マニフェストをデプロイする。

$ cn="registry.10.0.0.1.xip.io" envsubst < ./manifest/registry.yaml | kubectl -n registry apply --dry-run -f -
deployment.apps/registry created (dry run)
service/registry created (dry run)
ingress.extensions/registry created (dry run)

$ cn="registry.10.0.0.1.xip.io" envsubst < ./manifest/registry.yaml | kubectl -n registry apply -f -
deployment.apps/registry created
service/registry created
ingress.extensions/registry created

https でアクセスしてみる。

$ curl -k https://${cn}/v2/_catalog
{"repositories":[]}

5. docker を構成する

  • docker host の設定
    • ./certs/tls.crt/etec/docker/certs.d/${cn}/ca.crt へ配置する。
    • docker daemon の再起動は不要。
  • docker client の設定
    • docker login ${cn} する

docker image push ${cn}/name:tag が成功すれば OK。

Z. minikube の場合に必要なこと

起動するときに minikube start --insecure-registry ${cn}:443 が必要。

  • insecure-registry オプションは dockerd に直接指定される
    • systemdunit ファイルで ExecStart に反映されてた
  • docekrdsystemddocker.service として実行されている

6. kind を構成する

こちらを参照。

minikube で kind を動かす - yujioramaの日記

7. kind でイメージを実行する

レジストリの認証情報を登録する。

kubectl --context=kind create secret docker-registry local-registry \
  --docker-server=${cn} \
  --docker-username=testuser \
  --docker-password=pass01 \
  --docker-email=testuser@registry

サービスアカウントに imagePullSecrets を追加。

kubectl --context=kind patch serviceaccount default -p '{"imagePullSecrets": [{"name":"local-registry"}]}'

前の手順で push したイメージを実行。

kubectl --context=kind run d --rm -it ${cn}/debian:buster-slim

ここから上手くいってない。

いろいろ確認した結果

原因は containerd自己署名証明書による TLS 通信に対応してないことだった。

github.com

中間証明局の証明書を OS の証明書チェインに追加するとかそういう方法はあるけど、設定ではどうにもならなそう。

TLS 通信をしない insecure registry なら設定でどうにかなるんだけど。

参考リンク

minikube で kind を動かす

趣旨

  • k8s のマルチノードクラスタを試してみたくなった
  • kind を使ってみる
    • ただし minikube で作った k8s クラスタ(の docker) を再利用する
  • デプロイは完了するけど外からアクセスできない
    • 公開される IP アドレスが 127.0.0.1 に固定されてる
    • 公開されるポート番号がランダム
  • kind の実装眺めてたら設定ファイルで制御できそうなことがわかった
  • できた 👍

kind の設定ファイル

kind creaet cluster に次のような設定ファイルを指定するといい感じになります。

kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha4
networking:
  apiServerPort: 11734
  apiServerAddress: 192.168.0.123
nodes:
- role: control-plane

networking についてはソースコードのこの辺に書いてありました。

kind/types.go at master · kubernetes-sigs/kind · GitHub

クラスタの作成

kind は引数で設定項目の一部を上書きできるようにはなっておらず、完成している設定ファイルを渡す必要があります。

設定ファイルのテンプレートを用意して envsubst するのが無難な感じです。

$ kind --version
kind version v0.5.1

$ lsb_release  -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 10 (buster)
Release:        10
Codename:       buster

$ sudo apt install -y gettext-base

$ which envsubst
/usr/bin/envsubst

$ cat config.tmpl
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
networking:
  apiServerPort: $PORT
  apiServerAddress: $ADDRESS
nodes:
- role: control-plane

$ PORT=11374 ADDRESS=$(minikube ip) envsubst < config.tmpl > config.yaml && kind create cluster --config=./config.yaml --wait=10m
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.15.3) �
 ✓ Preparing nodes �
 ✓ Creating kubeadm config �
 ✓ Starting control-plane �️
 ✓ Installing CNI �
 ✓ Installing StorageClass �
 ✓ Waiting ≤ 10m0s for control-plane = Ready ⏳
 • Ready after 24s �
Cluster creation complete. You can now use the cluster with:

export KUBECONFIG="$(kind get kubeconfig-path --name="kind")"
kubectl cluster-info

$ kubectl --kubeconfig="$(kind get kubeconfig-path --name="kind")" cluster-info
Kubernetes master is running at https://192.168.0.123:11374
KubeDNS is running at https://192.168.0.123:11374/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

参考リンク

Windows 10 1903 へリモートデスクトップ接続すると数分で画面描画が固まる(操作はできてる)

理由はわからないけど。

とりあえずワークアラウンドがあるようなので適用して様子見してる。

social.technet.microsoft.com

Based on my research, it's an issue with Win 10 1809 to Win 10 1809.Now please have a try with the method below:

You can work around this by disabling UDP session used by MSTSC on your affected computer (the one where they're running the Remote Desktop client).

Run gpedit.msc.

Navigate to Computer Configuration > Administration Templates > Windows Components > Remote Desktop Services > Remote Desktop Connection Client.

Set the "Turn Off UDP On Client" setting to Enabled.

11回目の Microservices Patterns 読書会に参加したメモ

終わった後の飲み会で注文したローストチキン。 勉強会の内容とは関係ない。

javaee-study.connpass.com

簡単な経緯

  • 今回も恵比寿のレッドハットさんで開催
  • 参加者7人のうち、マイクロサービスやってる勢は2,3人くらい
  • 前回まではどうやって外部APIを利用するといいのか説明している章
  • 今回はどうやってテストするといいのか説明している章
  • 次回は 2019/12/14

「Testing microservices: Part 1」

あまり整理された状態ではない模様。 Testing microservices (Work in progress)

開発チームが実装するテストの話をしたいのは分かる。

テストピラミッドという形式で、レイヤの考え方によりテスト対象を区分けしている。 ユニット(ドメインの内部)、インテグレーション(内部と外部のインターフェイス)、コンポーネント(内部と外部のインターフェイス)、E2E(外部)

テストピラミッドの次にコンシューマー駆動契約テスト(CdC)の説明が出てくるあたり、とてもお勧めしたかったことがうかがわれる。読む側には辛い。

パターンとして独立させるくらいには特別視していることがわかる。

Service Integration Contract Test

後は構成要素ごとにユニットテストとしてどういうテストを書くべきかを説明している。

エンティティやバリューオブジェクトはビジネスロジックが含まれるからしっかりやったほうがよさそう。それはそうだ。

サービスやサーガ、コントローラーやイベントハンドラーはドメイン外部への依存があるからモックオブジェクトが必須になる。 外部サービスへの依存を切り離して実行できることに価値があるとするなら、、、まあやってもいいのかな。 なんかムダがあるような気がするけどうまく言語化できない。

「Testing microservices: Part 2」

インテグレーションテストとしてどういうテストを書くべきかを説明している。

persistence integration test はまあ普通に RDB へ保存できることを確認すればよい、そりゃそうだ。

REST エンドポイントの integration test の手法としてコンシューマー駆動契約テストをお勧めしている。 provider team と合意を形成するためのやりとりが発生するので、サービスが無軌道に成長するのを自然に妨げる仕組みだよなぁと思った

実況風のログ

最近は1セクションごとに気になるところを確認して、あれば議論するような進行形式になっています。

だいたいこんな感じのやりとりが行われていました。

断片しか記録できてませんが、実際はちゃんと納得できるまで議論されています。

Consumer Driven Contract Test

  • 形式をチェックするだけでは物足りない気がする
  • 注文して、注文がキャンセルされて、みたいな一連の流れをどこかでテストするべきなのでは。
    • そういう事前状態を宣言した契約をいくつか並べるのでは。
  • 複数のサービスから利用されているため、複数のE2Eを通過させないとリリースできないサービスがある。自律的にリリースできなくて苦しいので、CdCが救済手段になるかもしれないと考えている

Deployment Pipeline

  • テスト通過したら自動でリリースってそんなに一般的?
    • 段階的に少しずつ公開範囲を広げるやり方をやってる。
    • 内部APIはそうでもないけど、ユーザーの目に触れる改修は慎重に確認してる。
    • フィーチャーによってOKとするメトリクスは異なる。

エンティティのユニットテスト

  • エンティティに定義したロジックで外部サービスを呼び出す必要がある場合の方針に悩んでる。サービスへのアクセサを渡すのかプロキシを渡すのか、呼び出した結果の値を渡すのか?
  • ビジネスロジックをサービスに定義しがちなので、エンティティに定義するときはどうしてるのか疑問。
  • エンティティとサービスでは抽象度が合わないので、概念が不足しているのでは?
  • クロージャを渡してあげるみたいなことをしていることもあった。
  • イベント原理主義で考えるとエンティティの状態を変更することがなくなるので、テストする意義がなくなってくるなぁ。
  • 状態を型で表現する場合も。

まとめ

  • E2Eテストってどうやってるんですか。段階的リリースをしてるからやってない?
    • 依存関係も含むテストという意味ではやってます。
    • 開発環境で実行したり、テスト環境で実行したり。
  • デプロイメントを namespace 単位にしてたので、 namespace を跨ぐような依存関係があると困りがち。
  • このスタイルのユニットテストってムダになりそう。
  • 後になるとUI側のほうが理解できてるからE2Eを書きたくなっちゃう。そうすると頭でっかちになりがちなので悩ましい。
  • 障害事象と同じ抽象度で表現したいときはやはりE2Eを追加したくなる。
  • 個人的な見解だけど、E2Eを補足する低水準テストは書かなくてもいいんじゃないかと思ってる。
  • 脆いこと、実行に時間がかかること、この2点は本当に厳しい。

Persistence Integration Test

  • もうテスト用に H2 とか使わないんですかね。
    • 速度を優先するならありだと思います。どこに注目するか次第では。
  • MySQL をコンテナで使ってる。

Integration tests REST-based request/response style interactions

  • パラメータをハードコードしてるのはやや残念。コントラクトファイルから取り出せそうなものなのに。

スーパーカミオカンデの蓋の上に立ってきた


集合場所の 道の駅スカイドーム神岡。

簡単な経緯

講演会:スーパーカミオカンデによるニュートリノ観測の最前線

  • 中畑 雅行先生による講演が無料で開催されていた。
  • 参加者はほぼ全てスーパーカミオカンデの見学希望者だった。
  • 超新星爆発により生じる光(エネルギー)は、数百の恒星により恒星される星雲の発する光(エネルギー)に相当する。

実験設備の見学

  • 「実験施設は地下1000mにあります。」という言葉の意味を体験できた
    • 坑道の入り口から漏れ出てくる空気がとても冷たくて驚いた
    • 息が白くなるほど
    • 「電気で冷やしてる」のではなく「岩盤が冷たいから冷えている」そうだ
    • 地下1000mの世界ってもうよく分からなすぎる
  • スーパーカミオカンデの蓋の上の足場は鉄板でわりとガタガタしてた
    • 足下に40m以上の空洞があるのがわかってるからちょっと怖い
  • 天井はそんなに高くなかった
  • 大学とかで見られる実験室の規模を100倍くらいに広げたような印象

削除されたリモートブランチを upstream にしているローカルブランチを削除したい

そういう便利機能がありそうでなかったので自分でやるしかなかった。

git fetch --prune

git branch --format='%(refname:short) %(upstream:track)' | grep '\[gone\]' | awk '{print $1}' | xargs -r git branch -d

解説

git fetch --prune

リポジトリを同期する。

削除されたリモートブランチの remote refs も削除する。

git branch --format='%(refname:short) %(upstream:track)'

ローカルブランチ名に upstream の状況を表示する。

リモートブランチの状況 表示
リモートブランチが進んでいるとき <name> [behind <number>]
ローカルブランチが進んでいるとき <name> [ahead <number>]
リモートブランチが削除されているとき <name> [gone]

grep '\[gone\]'

リモートブランチが削除されたブランチ名だけを残す。

awk '{print $1}'

ブランチ名だけを残す。

xargs -r git branch -d

残っているブランチ名を指定して削除している。

面倒なときは git branch -D として強制的に削除すればいい。

参考