本記事は「Kubernetes2 Advent Calendar 2020」の8日目のエントリになります。よろしくお願いします。
内容としては、「Rook/Cephのブロックストレージとオブジェクトストレージを使ってGROWI WikiをKubernetesで動かす」の続き(機能追加)になります。
TL;DR
GROWI with "Elasticsearch and HackMD" on KubernetesをSkaffoldを使ってビルド&デプロイしてみました。
zaki-lknr/growi-on-k8s: GROWI Wiki on Kubernetes
前置き
先日、Rookと仲間たち、クラウドネイティブなストレージの Advent Calendar 2020の3日目のエントリで以下の記事を作成しました。
Rook/Cephのブロックストレージとオブジェクトストレージを使ってGROWI WikiをKubernetesで動かす - Qiita
この記事でのGROWIは、Elasticsearch連携は(オブジェクトストレージの記事としては蛇足かと思い)オプションということもあり省略していたので、改めてElasticsearchも設定して検索が使えるGROWI on Kubernetesをデプロイしてみよう、という内容になります。vm.max_map_count
のエラーを解消することができずに
GROWI with Elasticsearch
Docker Compose版のGROWIで使用されるElasticsearchの定義ベースにすすめます。
日本語プラグイン導入
GROWIで使うために、以下の日本語プラグインを導入します。
- analysis-kuromoji
- analysis-icu
Helmチャートの設定でどうにかできたらベストだったんですが、READMEのプラグインインストールの項を見る限り、イメージをビルドしないといけないみたいなので、ビルドします。
※ bitnamiのチャートだと、プラグインを設定で入れられるっぽいのでビルド不要で使えるかもしれないけど未確認
Dockerfile
ベースはGROWIのリポジトリにあるDocker Composeで使っているDockerfileを使用します。
ただし、この内容でビルドしたイメージをそのままKubernetesにデプロイしても下記エラーで起動に失敗します。
ERROR: [1] bootstrap checks failed
[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
ポイントは、コンテナ起動時のボリューム設定で挿入しているelasticsearch.yml
ファイルによる設定。
Kubernetesではコンテナ起動時にこの芸当は(NFSとかで事前に用意したファイルをpvとしてマウントしない限りはたぶん)できないので、イメージに組み込んでビルドします。
よって、Dockerfileはこんな感じ。
FROM docker.elastic.co/elasticsearch/elasticsearch:6.8.10
RUN bin/elasticsearch-plugin install analysis-kuromoji
RUN bin/elasticsearch-plugin install analysis-icu
COPY elasticsearch.yml /usr/share/elasticsearch/config/elasticsearch.yml
イメージにCOPY
するelasticsearch.yml
はこちら。
http.host: 0.0.0.0
# for elasticsearch-head
http.cors.enabled: true
http.cors.allow-origin: "*"
Skaffoldを使ってbuild & push & deploy (devモード)
Dockerイメージを手元でビルドし、Docker Hubへpushし、Kubernetesクラスタでpullします。
手作業でこれを繰り返すとまぁまぁ面倒なので、今回はこれらを全部一気にやってくれるSkaffoldというビルドツールを使ってみました。
(ずーーっと前から「Skaffoldを試す」というタスクに積んだままだったので、ついでにここで試し見ようというやつです)
Google発のコンテナアプリケーション開発支援ツール「Skaffold」や「Kaniko」を使ってみる | さくらのナレッジ
Skaffold設定
インストールは(Linuxなら)バイナリをダウンロードしてパスの通ったパス(/usr/local/bin
など)に置けばOKです。
Skaffoldコマンドを使うための準備としては
-
docker login
しておき、skaffold
実行ユーザアカウントでコンテナレジストリへpush
ができる状態にする -
$HOME/.kube/config
を設定しておき、skaffold
実行ユーザアカウントでkubectl
を使ったクラスタへのマニフェストのデプロイができる状態にする
の2点です。(2についてはhelm
やistioctl
と同じ要領。たぶん)
docker push
を行うので、コンテナレジストリにDocker Hubを使用する場合はアカウント作成と、push先のリポジトリを用意しておく必要があります。
Skaffoldビルド・デプロイ設定
前述のDockerfile
とDockerfile
から参照(イメージへコピー)する外部ファイルelasticsearch.yml
と同じディレクトリ(←構成を簡単にするため)に以下の「デプロイ用Kubernetesマニフェストファイル」と「Skaffold定義ファイル」を作成します。
デプロイ用Kubernetesマニフェストファイルは特に通常通りなので省略。
例えばDeploymentを使ってこんな感じ(あとでStatefulSetに変更しました)。
skaffold.yaml
については、一つのイメージをbuild/push/deployする最小限な記述としてはこんな感じ。
apiVersion: skaffold/v1
kind: Config
build:
artifacts:
- image: zakihmkc/growi-elasticsearch
context: ./
docker:
dockerfile: ./Dockerfile
deploy:
kubectl:
manifests:
- ./k8s-deploy.yaml
詳細はリファレンスを確認してください。
ポイントはbuild
セクションのartifacts
で、ビルドするイメージの名前==push
先のリポジトリ名を設定しておくことで、docker build
からdocker push
まで一気に行われます。
ビルドのcontext
とdocker.dockerfile
はDocker Composeでビルドするときと同じでディレクトリの指定とDockerfile
の指定。「開発」「ステージング」「本番」のようにファイルを分けるときに使うと良いです。
また、deploy
セクションでpushされたイメージをクラスターへデプロイするためのマニフェストファイルを指定することで、pushに成功した後にデプロイまで処理されます。
devモードで実行するには、サブコマンドdev
を指定して実行します。
同じdevという名前にして分かりにくいけど、ビルドするイメージにテンポラリのタグを付与するために-t dev
を追加。
[zaki@cloud-dev elasticsearch]$ ls
Dockerfile elasticsearch.yml k8s-deploy.yaml skaffold.yaml
[zaki@cloud-dev elasticsearch]$ skaffold dev -t dev
Listing files to watch...
- zakihmkc/growi-elasticsearch
Generating tags...
- zakihmkc/growi-elasticsearch -> zakihmkc/growi-elasticsearch:dev
Checking cache...
- zakihmkc/growi-elasticsearch: Not found. Building
Building [zakihmkc/growi-elasticsearch]...
Sending build context to Docker daemon 3.072kB
Step 1/4 : FROM docker.elastic.co/elasticsearch/elasticsearch:6.8.10
---> ffa00077159c
Step 2/4 : RUN bin/elasticsearch-plugin install analysis-kuromoji
---> Using cache
---> 6966b4d5e5bf
Step 3/4 : RUN bin/elasticsearch-plugin install analysis-icu
---> Using cache
---> 7e8711478ef2
Step 4/4 : COPY elasticsearch.yml /usr/share/elasticsearch/config/elasticsearch.yml
---> 11556383ec5f
Successfully built 11556383ec5f
Successfully tagged zakihmkc/growi-elasticsearch:dev
The push refers to repository [docker.io/zakihmkc/growi-elasticsearch]
06e6281e2c86: Preparing
c2255a3188da: Preparing
[... snip ...]
edf3aa290fb3: Layer already exists
06e6281e2c86: Pushed
dev: digest: sha256:c260d4c6f595b15163c9f7a3d88fcb6f790995f9f63ac519baaa94af49bc5bf2 size: 2421
Tags used in deployment:
- zakihmkc/growi-elasticsearch -> zakihmkc/growi-elasticsearch:dev@sha256:c260d4c6f595b15163c9f7a3d88fcb6f790995f9f63ac519baaa94af49bc5bf2
Starting deploy...
- deployment.apps/elasticsearch created
- service/elasticsearch created
Waiting for deployments to stabilize...
- deployment/elasticsearch: creating container elasticsearch
- pod/elasticsearch-76d9c6d6f6-nqtjs: creating container elasticsearch
- deployment/elasticsearch is ready.
Deployments stabilized in 5.595258289s
Press Ctrl+C to exit
Watching for changes...
[elasticsearch] [2020-12-04T14:10:06,213][INFO ][o.e.e.NodeEnvironment ] [TQOy5aT] using [1] data paths, mounts [[/ (rootfs)]], net usable_space [42.1gb], net total_space [58.9gb], types [rootfs]
[elasticsearch] [2020-12-04T14:10:06,215][INFO ][o.e.e.NodeEnvironment ] [TQOy5aT] heap size [1gb], compressed ordinary object pointers [true]
[elasticsearch] [2020-12-04T14:10:06,218][INFO ][o.e.n.Node ] [TQOy5aT] node name derived from node ID [TQOy5aTYQuKGZ35Crwr6Iw]; set [node.name] to override
[elasticsearch] [2020-12-04T14:10:06,218][INFO ][o.e.n.Node ] [TQOy5aT] version[6.8.10], pid[1], build[default/docker/537cb22/2020-05-28T14:47:19.882936Z], OS[Linux/3.10.0-1127.13.1.el7.x86_64/amd64], JVM[AdoptOpenJDK/OpenJDK 64-Bit Server VM/14.0.1/14.0.1+7]
[elasticsearch] [2020-12-04T14:10:06,218][INFO ][o.e.n.Node ] [TQOy5aT] JVM arguments [-Xms1g, -Xmx1g, -XX:+UseG1GC, -XX:G1ReservePercent=25, -XX:InitiatingHeapOccupancyPercent=30, -Des.networkaddress.cache.ttl=60, -Des.networkaddress.cache.negative.ttl=10, -XX:+AlwaysPreTouch, -Xss1m, -Djava.awt.headless=true, -Dfile.encoding=UTF-8, -Djna.nosys=true, -XX:-OmitStackTraceInFastThrow, -XX:+ShowCodeDetailsInExceptionMessages, -Dio.netty.noUnsafe=true, -Dio.netty.noKeySetOptimization=true, -Dio.netty.recycler.maxCapacityPerThread=0, -Dlog4j.shutdownHookEnabled=false, -Dlog4j2.disable.jmx=true, -Djava.io.tmpdir=/tmp/elasticsearch-16478887452689113828, -XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath=data, -XX:ErrorFile=logs/hs_err_pid%p.log, -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m, -Djava.locale.providers=COMPAT, -XX:UseAVX=2, -Des.cgroups.hierarchy.override=/, -Des.path.home=/usr/share/elasticsearch, -Des.path.conf=/usr/share/elasticsearch/config, -Des.distribution.flavor=default, -Des.distribution.type=docker]
[elasticsearch] [2020-12-04T14:10:09,582][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [aggs-matrix-stats]
[elasticsearch] [2020-12-04T14:10:09,582][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [analysis-common]
[elasticsearch] [2020-12-04T14:10:09,582][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [ingest-common]
[elasticsearch] [2020-12-04T14:10:09,582][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [ingest-geoip]
[elasticsearch] [2020-12-04T14:10:09,582][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [ingest-user-agent]
[elasticsearch] [2020-12-04T14:10:09,583][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [lang-expression]
[elasticsearch] [2020-12-04T14:10:09,583][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [lang-mustache]
[elasticsearch] [2020-12-04T14:10:09,583][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [lang-painless]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [mapper-extras]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [parent-join]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [percolator]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [rank-eval]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [reindex]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [repository-url]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [transport-netty4]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [tribe]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-ccr]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-core]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-deprecation]
[elasticsearch] [2020-12-04T14:10:09,584][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-graph]
[elasticsearch] [2020-12-04T14:10:09,585][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-ilm]
[elasticsearch] [2020-12-04T14:10:09,585][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-logstash]
[elasticsearch] [2020-12-04T14:10:09,585][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-ml]
[elasticsearch] [2020-12-04T14:10:09,585][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-monitoring]
[elasticsearch] [2020-12-04T14:10:09,585][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-rollup]
[elasticsearch] [2020-12-04T14:10:09,585][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-security]
[elasticsearch] [2020-12-04T14:10:09,585][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-sql]
[elasticsearch] [2020-12-04T14:10:09,585][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-upgrade]
[elasticsearch] [2020-12-04T14:10:09,586][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded module [x-pack-watcher]
[elasticsearch] [2020-12-04T14:10:09,595][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded plugin [analysis-icu]
[elasticsearch] [2020-12-04T14:10:09,595][INFO ][o.e.p.PluginsService ] [TQOy5aT] loaded plugin [analysis-kuromoji]
[elasticsearch] [2020-12-04T14:10:16,226][INFO ][o.e.x.s.a.s.FileRolesStore] [TQOy5aT] parsed [0] roles from file [/usr/share/elasticsearch/config/roles.yml]
[elasticsearch] [2020-12-04T14:10:17,251][INFO ][o.e.x.m.p.l.CppLogMessageHandler] [TQOy5aT] [controller/79] [Main.cc@109] controller (64 bit): Version 6.8.10 (Build a5f7163bca0250) Copyright (c) 2020 Elasticsearch BV
[elasticsearch] [2020-12-04T14:10:17,994][INFO ][o.e.d.DiscoveryModule ] [TQOy5aT] using discovery type [zen] and host providers [settings]
[elasticsearch] [2020-12-04T14:10:18,943][INFO ][o.e.n.Node ] [TQOy5aT] initialized
[elasticsearch] [2020-12-04T14:10:18,943][INFO ][o.e.n.Node ] [TQOy5aT] starting ...
[elasticsearch] [2020-12-04T14:10:19,069][INFO ][o.e.t.TransportService ] [TQOy5aT] publish_address {127.0.0.1:9300}, bound_addresses {127.0.0.1:9300}
[elasticsearch] [2020-12-04T14:10:19,086][WARN ][o.e.b.BootstrapChecks ] [TQOy5aT] max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
[elasticsearch] [2020-12-04T14:10:22,134][INFO ][o.e.c.s.MasterService ] [TQOy5aT] zen-disco-elected-as-master ([0] nodes joined), reason: new_master {TQOy5aT}{TQOy5aTYQuKGZ35Crwr6Iw}{mDuQTY1KS1q1Es7e0DDjLA}{127.0.0.1}{127.0.0.1:9300}{ml.machine_memory=8181829632, xpack.installed=true, ml.max_open_jobs=20, ml.enabled=true}
[elasticsearch] [2020-12-04T14:10:22,138][INFO ][o.e.c.s.ClusterApplierService] [TQOy5aT] new_master {TQOy5aT}{TQOy5aTYQuKGZ35Crwr6Iw}{mDuQTY1KS1q1Es7e0DDjLA}{127.0.0.1}{127.0.0.1:9300}{ml.machine_memory=8181829632, xpack.installed=true, ml.max_open_jobs=20, ml.enabled=true}, reason: apply cluster state (from master [master {TQOy5aT}{TQOy5aTYQuKGZ35Crwr6Iw}{mDuQTY1KS1q1Es7e0DDjLA}{127.0.0.1}{127.0.0.1:9300}{ml.machine_memory=8181829632, xpack.installed=true, ml.max_open_jobs=20, ml.enabled=true} committed version [1] source [zen-disco-elected-as-master ([0] nodes joined)]])
[elasticsearch] [2020-12-04T14:10:22,197][INFO ][o.e.h.n.Netty4HttpServerTransport] [TQOy5aT] publish_address {10.244.127.102:9200}, bound_addresses {0.0.0.0:9200}
[elasticsearch] [2020-12-04T14:10:22,197][INFO ][o.e.n.Node ] [TQOy5aT] started
[elasticsearch] [2020-12-04T14:10:22,223][WARN ][o.e.x.s.a.s.m.NativeRoleMappingStore] [TQOy5aT] Failed to clear cache for realms [[]]
[elasticsearch] [2020-12-04T14:10:22,262][INFO ][o.e.g.GatewayService ] [TQOy5aT] recovered [0] indices into cluster_state
[elasticsearch] [2020-12-04T14:10:22,874][INFO ][o.e.c.m.MetaDataIndexTemplateService] [TQOy5aT] adding template [.watches] for index patterns [.watches*]
[elasticsearch] [2020-12-04T14:10:22,941][INFO ][o.e.c.m.MetaDataIndexTemplateService] [TQOy5aT] adding template [.watch-history-9] for index patterns [.watcher-history-9*]
[elasticsearch] [2020-12-04T14:10:22,964][INFO ][o.e.c.m.MetaDataIndexTemplateService] [TQOy5aT] adding template [.triggered_watches] for index patterns [.triggered_watches*]
[elasticsearch] [2020-12-04T14:10:22,992][INFO ][o.e.c.m.MetaDataIndexTemplateService] [TQOy5aT] adding template [.monitoring-logstash] for index patterns [.monitoring-logstash-6-*]
[elasticsearch] [2020-12-04T14:10:23,044][INFO ][o.e.c.m.MetaDataIndexTemplateService] [TQOy5aT] adding template [.monitoring-es] for index patterns [.monitoring-es-6-*]
[elasticsearch] [2020-12-04T14:10:23,092][INFO ][o.e.c.m.MetaDataIndexTemplateService] [TQOy5aT] adding template [.monitoring-alerts] for index patterns [.monitoring-alerts-6]
[elasticsearch] [2020-12-04T14:10:23,255][INFO ][o.e.c.m.MetaDataIndexTemplateService] [TQOy5aT] adding template [.monitoring-beats] for index patterns [.monitoring-beats-6-*]
[elasticsearch] [2020-12-04T14:10:23,310][INFO ][o.e.c.m.MetaDataIndexTemplateService] [TQOy5aT] adding template [.monitoring-kibana] for index patterns [.monitoring-kibana-6-*]
[elasticsearch] [2020-12-04T14:10:23,479][INFO ][o.e.l.LicenseService ] [TQOy5aT] license [93d6e9a7-978d-4035-8052-e26c27e49afe] mode [basic] - valid
ここで表示が止まってますが、これ、kubectl logs -f
のログが出ています。
別のターミナルでkubectl get pod
すれば以下の通り。
[zaki@cloud-dev elasticsearch]$ kubectl get pod
NAME READY STATUS RESTARTS AGE
elasticsearch-76d9c6d6f6-nqtjs 1/1 Running 0 5m41s
ちゃんと動いています。
ログの最後じゃないので分かりづらいけど、多分このログが起動成功の目印だと思う。(多分)
[elasticsearch] [2020-12-04T14:10:22,197][INFO ][o.e.h.n.Netty4HttpServerTransport] [TQOy5aT] publish_address {10.244.127.102:9200}, bound_addresses {0.0.0.0:9200}
[elasticsearch] [2020-12-04T14:10:22,197][INFO ][o.e.n.Node ] [TQOy5aT] started
実行中のイメージのtagはこの通りだけど、ハッシュ値が付与されている状態。
[zaki@cloud-dev elasticsearch]$ kubectl get pod elasticsearch-76d9c6d6f6-nqtjs -o jsonpath='{.spec.containers[].image}'; echo
zakihmkc/growi-elasticsearch:dev@sha256:c260d4c6f595b15163c9f7a3d88fcb6f790995f9f63ac519baaa94af49bc5bf2
ビルドしたイメージが正常に動作するのが確認できたのでCtrl-c
で停止します。
[elasticsearch] [2020-12-04T14:10:23,479][INFO ][o.e.l.LicenseService ] [TQOy5aT] license [93d6e9a7-978d-4035-8052-e26c27e49afe] mode [basic] - valid
^CCleaning up...
- deployment.apps "elasticsearch" deleted
- service "elasticsearch" deleted
There is a new version (1.17.1) of Skaffold available. Download it from:
https://github.com/GoogleContainerTools/skaffold/releases/tag/v1.17.1
するとpodも停止します。
便利ですね。
[zaki@cloud-dev elasticsearch]$ kubectl get pod
NAME READY STATUS RESTARTS AGE
elasticsearch-76d9c6d6f6-nqtjs 1/1 Terminating 0 11m
pushされたイメージはこの通り。
そして、Skaffoldのdevモードが更に便利なところは、Ctrl-c
で停止するまの待機状態の間は、Dockerfileやデプロイ用マニフェストを変更すると、ファイルの変更を検知してリビルドから再デプロイまで自動でやってくれるため、開発中のトライアルアンドエラーの最中はデプロイ作業に時間を割かずに済むため、開発に集中できます。
runモードでbuild & push & deploy
引数をdev
からrun
に変更します。
ついでに、tagもリリースバージョンっぽくしてみます。
[zaki@cloud-dev elasticsearch]$ skaffold run -t 6.8.10-ja
Generating tags...
- zakihmkc/growi-elasticsearch -> zakihmkc/growi-elasticsearch:6.8.10-ja
Checking cache...
- zakihmkc/growi-elasticsearch: Found. Tagging
Tags used in deployment:
- zakihmkc/growi-elasticsearch -> zakihmkc/growi-elasticsearch:6.8.10-ja@sha256:c260d4c6f595b15163c9f7a3d88fcb6f790995f9f63ac519baaa94af49bc5bf2
Starting deploy...
- deployment.apps/elasticsearch created
- service/elasticsearch created
Waiting for deployments to stabilize...
- deployment/elasticsearch is ready.
Deployments stabilized in 3.40209576s
You can also run [skaffold run --tail] to get the logs
There is a new version (1.17.1) of Skaffold available. Download it from:
https://github.com/GoogleContainerTools/skaffold/releases/tag/v1.17.1
[zaki@cloud-dev elasticsearch]$
今度はバックグラウンドで処理が行われるため、プロンプトが返ってきます。
[zaki@cloud-dev elasticsearch]$ kubectl get pod
NAME READY STATUS RESTARTS AGE
elasticsearch-89688ff58-tw969 1/1 Running 0 13s
新しいtagもpushされています。(中身は変わっていないのでdev
と同じ)
runモードでデプロイしたpod類の削除
停止・削除するには、skaffold delete
を実行します。
[zaki@cloud-dev elasticsearch]$ skaffold delete
Cleaning up...
- deployment.apps "elasticsearch" deleted
- service "elasticsearch" deleted
StatefulSetバージョン
最初からそうすればよかったのにと思いつつ、ワークロードをStatefulSetに変更してデプロイ。
また、実行時に-n <namespace>
を付与して、デプロイ先をGROWI本体と同じネームスペースを指定しています。
[zaki@cloud-dev elasticsearch (features/elasticsearch)]$ skaffold run -t 6.8.10-ja -n growi-on-k8s
Generating tags...
- zakihmkc/growi-elasticsearch -> zakihmkc/growi-elasticsearch:6.8.10-ja
Checking cache...
- zakihmkc/growi-elasticsearch: Found Remotely
Tags used in deployment:
- zakihmkc/growi-elasticsearch -> zakihmkc/growi-elasticsearch:6.8.10-ja@sha256:c260d4c6f595b15163c9f7a3d88fcb6f790995f9f63ac519baaa94af49bc5bf2
Starting deploy...
- statefulset.apps/elasticsearch created
- service/elasticsearch created
Waiting for deployments to stabilize...
Deployments stabilized in 10.508539ms
You can also run [skaffold run --tail] to get the logs
There is a new version (1.17.1) of Skaffold available. Download it from:
https://github.com/GoogleContainerTools/skaffold/releases/tag/v1.17.1
[zaki@cloud-dev elasticsearch (features/elasticsearch)]$ kc get pod,svc,pvc -n growi-on-k8s
NAME READY STATUS RESTARTS AGE
pod/elasticsearch-0 0/1 ContainerCreating 0 9s
pod/growi-app-fd6fcdd9d-vsdjj 1/1 Running 14 7d
pod/growi-mongo-0 1/1 Running 3 7d
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/elasticsearch ClusterIP 10.99.155.247 <none> 9200/TCP 9s
service/growi-app LoadBalancer 10.96.23.173 192.168.0.185 3000:32644/TCP 7d
service/growi-mongo ClusterIP 10.106.230.143 <none> 27017/TCP 7d
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/es-data-elasticsearch-0 Bound pvc-f052985f-b3a9-408f-8343-d09b1cbf8ff7 8Gi RWO rook-ceph-block 9s
persistentvolumeclaim/mongo-config-growi-mongo-0 Bound pvc-6b29acb4-bad6-4b01-9135-4302bf1f35d3 256Mi RWO rook-ceph-block 7d1h
persistentvolumeclaim/mongo-db-growi-mongo-0 Bound pvc-21ea3319-5487-4b10-83d3-a9c076d30b28 4Gi RWO rook-ceph-block 7d1h
GROWI WikiのElasticsearch設定
Elasticsearchの設定が無い場合はこの通り。
GROWIにElasticsearchを組み込むには、Docker Compose版のdocker-compose.yml
の通り、環境変数ELASTICSEARCH_URI
にElasticsearchのURLを設定します。
Kubernetes環境ではService経由のpod間通信になるので、Service名(elasticsearch)を記述します。
containers:
- image: weseek/growi:4
name: growi-app
env:
- name: MONGO_URI
value: mongodb://growi-mongo:27017/growi
- name: ELASTICSEARCH_URI
value: http://elasticsearch:9200/growi
- name: PASSWORD_SEED
value: changeme
このマニフェストでデプロイし直せば、この通り画面上部に検索フィールドが表示されます。
設定のメニューの「全文検索管理」を表示すると、この通りElasticsearchが有効になっています。
これで、GROWIとElasticsearchが連携設定され、検索が使えるようになりました。
GROWI with HackMD
GROWIのWikiはデフォルトでは同じページを複数ユーザーで同時編集は出来ません。
(やってしまった場合は、後から保存しようとしたユーザー側でconflictとなり、手動でマージする必要があります)
ただし、HackMDと連携することで、同時編集ができるようにする機能が用意されているので、(これはイメージのビルドが必要だけど日本語プラグイン入りElasticsearchでイメージビルドしてるのでそのままのノリで)GROWI on Kubernetesでもやってみます。
HackMD連携も、公式のDocker Composeが公開されています。
現在のdocker-compose.ymlの内容を整理すると、以下の通り。
- MariaDBをデプロイ
- GROWI用HackMDイメージをビルド
- GROWI用HackMDをデプロイ
-
環境変数は以下の通り
- GROWI本体のブラウザからアクセスする際のURL
- MariaDB接続先
- HackMD単体でブラウザからアクセスできるようにしておく
-
環境変数は以下の通り
- GROWI本体の環境変数追加
- GROWI本体(pod)から見たHackMDのURL
- ブラウザからアクセスする際のHackMDのURL
MariaDB
Helmとかで別途入れてもいい気もするけど、GROWIのdocker-compose.ymlだとcharactersetの引数設定があるので、これベースにマニフェスト作成。
mariadb:
image: mariadb:10.3
command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
environment:
- MYSQL_USER=hackmd
- MYSQL_PASSWORD=hackmdpass
- MYSQL_DATABASE=hackmd
- MYSQL_RANDOM_ROOT_PASSWORD=true
ただしこれをそのままKubernetes用マニフェストに書き換えると、以下のエラーでデプロイに失敗します。
2020-12-05 0:45:49 0 [Note] mysqld (mysqld 10.3.27-MariaDB-1:10.3.27+maria~focal) starting as process 1 ...
mysqld: Please consult the Knowledge Base to find out how to run mysqld as root!
2020-12-05 0:45:49 0 [ERROR] Aborting
ポイントはcommand
の部分。
Kubernetesのマニフェストではcommand
でなくargs
を使うとうまく動作します。
spec:
containers:
- image: mariadb:10.3
name: hackmd
args:
- mysqld
- --character-set-server=utf8
- --collation-server=utf8_general_ci
env:
- name: MYSQL_USER
value: hackmd
- name: MYSQL_PASSWORD
GROWI用HackMDイメージビルドとデプロイ
これもSkaffoldを使います。
Dockerfileや、ビルド時にイメージに組み込む設定ファイル類は公式で公開されているものをそのまま使用できます。
https://github.com/weseek/growi-docker-compose/tree/master/hackmd
このファイル類に加えて、Elasticsearchのビルドと同様に以下のファイルを準備。
環境変数にGROWI本体の(ブラウザからアクセスする際の)URL設定が必要なので、設定しておきます。
build/push/deployの手順・内容はElasticsearchと同じなので省略します。
作成したファイル一式はGitHub参照してください。
デプロイに成功しtype:LoadBalancerなどを使って外部公開すると、以下のようにブラウザでアクセスできます。
GROWIの設定時に、ブラウザからアクセスするためのURLが必要なので確認しておきます。
GROWI WikiのHackMD設定
前述の通り、GROWI側にもHackMDがどこにいるかの設定が必要なので、環境変数を追加します。
HACKMD_URI_FOR_SERVER
はElasticsearchと同じく、GROWIのpodからHackMDのpodへのpod間通信のためService名を記述します。
HACKMD_URI
はブラウザからアクセスする際のHackMDのURLを設定します。
containers:
- image: weseek/growi:4
name: growi-app
env:
- name: MONGO_URI
value: mongodb://growi-mongo:27017/growi
- name: ELASTICSEARCH_URI
value: http://elasticsearch:9200/growi
- name: PASSWORD_SEED
value: changeme
- name: HACKMD_URI_FOR_SERVER
value: http://hackmd:3000
- name: HACKMD_URI
value: http://192.168.0.186:3000
この設定でGROWI本体を再デプロイしてブラウザでアクセス、ページ編集画面の隣にある「HackMD」ボタンを押下、
すると以下のように、HackMDを使って編集開始するためのボタンが表示されます。
ボタン押下すると、
あれ……?
残念ながらこれ原因が分かりませんでしたが、ChromeとEdgeだとダメでしたが、Firefoxだとアクセスできました。
もう一つブラウザを起動してアクセスすると、画面右上のオンライン数が2になり、それぞれのブラウザでの編集状態(カーソルなど)が表示されます。
ちなみにDocker Compose版だとChromeでもアクセスできるので、今回のtype:LoadBalancerを使用したKubernetes環境だと、GROWI本体とHackMDがブラウザから見ると別のIPアドレス(ホスト)にデプロイされていてかつHTTPSでもないので、Chromeのセキュリティ機能的な制限かなと思ってますが、このあたりで力尽きました。
(GROWIもHackMDもkubectl logs
で確認する限りそれっぽいエラーログは何も出ていない)
Chromeのデベロッパーツールで見るとこんな感じで、AUTH failed: No cookie transmitted.
というエラーが出ています。
まとめと課題
Skaffoldを使うことで、コンテナイメージのbuild/push/deployを一度に出来るので、大変効率的になります。
特にdevモードの場合は、ファイルの変更を検知して自動でイメージのリビルドからクラスタへのデプロイまで行われるので、コードの作成に集中できます。
GROWI on Kubernetesについても、ElasticsearchおよびHackMDをKubernetesへデプロイすることで、検索および同時編集が可能になりました。
今回はtype:LoadBalancerを使ってGROWI本体とHackMDを外部公開したため、Chromeアクセス時に期待する動作を確認できませんでしたが、Ingressなど別の方法で実装すると改善されるかもしれません。
またtype:LoadBalancerの欠点として、デプロイするまで外部アクセス用のアドレスが分からないため、GROWI本体とHackMDでお互いの外部アクセス用アドレスを初回デプロイ前は設定することができません。
これも改善の余地があり、Ingressでできるかもしれないし、ExternalDNSも使えるかもしれないので検討してみたいです。
自宅 k8s クラスタのサービスに FQDN で繋がるようにした - ののし.log
(参考) エラーパターン
Elasticsearch
[elasticsearch] ERROR: [1] bootstrap checks failed
[elasticsearch] [1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
このエラーは
FROM docker.elastic.co/elasticsearch/elasticsearch:6.8.10
RUN bin/elasticsearch-plugin install analysis-kuromoji
RUN bin/elasticsearch-plugin install analysis-icu
この内容のDockerfileで日本語プラグインがインストールされたカスタムイメージをKubernetesにデプロイすると発生しました。
以下のファイルを/usr/share/elasticsearch/config/elasticsearch.yml
置いておくと回避できます。
http.host: 0.0.0.0
# for elasticsearch-head
http.cors.enabled: true
http.cors.allow-origin: "*"
MariaDB
Docker Composeでcommand
を使ってデプロイすると、元々のイメージで指定されていたdocker-entrypoint.sh
とcommand
指定の引数がセットで適用されるけれど、Kubernetesのpodのパラメタでcommand
を使うと、どうやらdocker-entrypoint.sh
の情報が上書きされてmysqld
を直接実行して起動に失敗します。
用意されているDocker Composeで正常動作しているMariaDBのコンテナをdocker inspect
でパラメタ類を確認すると以下の通り。
{
"Cmd": [
"mysqld",
"--character-set-server=utf8",
"--collation-server=utf8_general_ci"
],
"Image": "mariadb:10.3",
"Volumes": {
"/var/lib/mysql": {}
},
"WorkingDir": "",
"Entrypoint": [
"docker-entrypoint.sh"
],
}
Kubernetes上でcommand
指定でcharset等の指定を行った場合のエラーメッセージ
2020-12-05 0:45:49 0 [Note] mysqld (mysqld 10.3.27-MariaDB-1:10.3.27+maria~focal) starting as process 1 ...
mysqld: Please consult the Knowledge Base to find out how to run mysqld as root!
2020-12-05 0:45:49 0 [ERROR] Aborting
上記のメッセージでググるとヒットする(基本的に非コンテナ環境だと思われる)回避策の--user=root
を追加すると
2020-12-05 0:46:37 0 [ERROR] Could not open mysql.plugin table. Some plugins may be not loaded
2020-12-05 0:46:37 0 [ERROR] Can't open and lock privilege tables: Table 'mysql.servers' doesn't exist
2020-12-05 0:46:37 0 [Note] Server socket created on IP: '::'.
2020-12-05 0:46:37 0 [ERROR] Fatal error: Can't open and lock privilege tables: Table 'mysql.user' doesn't exist
となり、同様に回避策をググって
- --user=mysql
- --datadir=/var/lib/mysql
に設定変更しても改善せず。
Docker Composeでの動作状態と比較してみるとcommand
を使うとdocker-entrypoint.sh
が使われずに起動しようとするので失敗していた、という状態でした。
これは、Docker単体でも、mysqld
を単体で実行すると同じエラーで起動失敗します。
$ docker run -it --rm --name mariadb mariadb:10.3 bash
root@b8a277c49a9d:/# mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
2020-12-05 11:27:44 0 [Note] mysqld (mysqld 10.3.27-MariaDB-1:10.3.27+maria~focal) starting as process 8 ...
mysqld: Please consult the Knowledge Base to find out how to run mysqld as root!
2020-12-05 11:27:44 0 [ERROR] Aborting