Skaffoldを用いてローカルでk8sにデプロイするJavaアプリの開発を行なう
はじめに
最近身の回りでSkaffoldという名前をよく聞くようになりまして、ちょっと気になって調べたら面白そうだったし、今後使っていきそうな雰囲気を感じたので、ちょっとさわっておこうかと思います。
Skaffoldとは?
Skaffoldはk8sネイティブなアプリケーションの開発をサポートしてくれるコマンドラインツールです。
k8sに対するBuild、Push、Deploy等をサポートしてくれます。
大まかには以下のような機能や特徴があります。
- ローカルでの開発において、のソースコードの変更を検知して、自動でBuild、Push、Deployまでのサポート。
- ローカルの開発において、ログに対するサポートとポートフォワードのサポート
git clone
とskaffold run
の実行で様々な環境でアプリを動作させることが可能- Skaffoldのprofile, local user config, environment variables, flags などの機能を使って環境ごとの設定を組み込むことが可能
skaffold render
コマンドを用いてKubernetesマニフェストのテンプレートをレンダリングすることによって、GitOpsワークフローをサポート- Clusterは無くクライアント再度のみで独立している
skaffold.yaml
ファイルによって宣言的で、プラガブルな設定が可能
Skaffold自体はCI/CDにおけるワークフローのサポートも行っているようですが、今回のこのブログではローカルでのアプリケーション開発におけるいくつかの機能を試してみたいと思います。
また、今回はJavaアプリケーションで開発を行ってみようと思います。
使ってみる
動作環境
ローカルのクラスタはMinikube(Docker Drive)を用いて構築します。
$ uname -srvmpio Linux 5.4.0-77-generic #86-Ubuntu SMP Thu Jun 17 02:35:03 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux $ lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: Ubuntu Description: Ubuntu 20.04.2 LTS Release: 20.04 $ minikube version minikube version: v1.21.0 commit: 76d74191d82c47883dc7e1319ef7cebd3e00ee11 $ kubectl version -o yaml clientVersion: buildDate: "2021-03-18T01:10:43Z" compiler: gc gitCommit: 6b1d87acf3c8253c123756b9e61dac642678305f gitTreeState: clean gitVersion: v1.20.5 goVersion: go1.15.8 major: "1" minor: "20" platform: linux/amd64 serverVersion: buildDate: "2021-05-12T12:32:49Z" compiler: gc gitCommit: 132a687512d7fb058d0f5890f07d4121b3f0a2e2 gitTreeState: clean gitVersion: v1.20.7 goVersion: go1.15.12 major: "1" minor: "20" platform: linux/amd64
Skaffoldのインストール
Skaffoldをインストールするためには以下のコマンドを叩きます。
$ curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && \ sudo install skaffold /usr/local/bin/ $ skaffold version v1.27.0
その他、MacやWindows、Dockerでのインストールはこちらをご確認ください。
サンプルアプリを作成しておく
Skaffoldを使ってデプロイやディバグなどの機能を試すために開発対象となるアプリを作っておきます。
特に深い意図はないのですがSpringを使ってやろうかと思います。
Spring Initializrで以下の設定でアプリを作成します。
依存はWebだけを追加してます。
Skaffoldプロジェクトを初期化する
ダウンロードしてきたプロジェクトを解凍して、プロジェクトのルートに移動し以下のコマンドを実行します。
Skaffoldプロジェクトを初期化するには、skaffold init
コマンドを用います。
skaffold init
コマンドは実行するとプロジェクトをスキャンし、以下のようなファイルを見つけるとその構成に合わせた設定を行ってくれます。
ちなみに500MB以上のファイルは無視されるようです。
例えば、こんかいのケースではいくつかの選択肢を提示してくれます。
$ skaffold init --generate-manifests ? Select port to forward for pom-xml-image (leave blank for none): 8080
--XXenableJibInit
フラグや--XXenableBuildpacksInit
フラグを使えば、それぞれJibやBuildpacksを用いた構成を作ることも可能なようです。
--generate-manifests
フラグはマニフェストの生成まで行ってもらうために使用しています。このフラグを使用しない場合は自分で作成したdeployment.yamlを用いることになります。
今回の場合はpom.xml
のみが検知され、Buildpack
を用いた設定がされます。
$ skaffold init --generate-manifests ? Select port to forward for pom-xml-image (leave blank for none): 8080 adding manifest path deployment.yaml for image pom-xml-image apiVersion: skaffold/v2beta18 kind: Config metadata: name: skaffold-sample build: artifacts: - image: pom-xml-image buildpacks: builder: gcr.io/buildpacks/builder:v1 deploy: kubectl: manifests: - deployment.yaml portForward: - resourceType: service resourceName: pom-xml-image port: 8080 deployment.yaml - apiVersion: v1 kind: Service metadata: name: pom-xml-image labels: app: pom-xml-image spec: ports: - port: 8080 protocol: TCP clusterIP: None selector: app: pom-xml-image --- apiVersion: apps/v1 kind: Deployment metadata: name: pom-xml-image labels: app: pom-xml-image spec: replicas: 1 selector: matchLabels: app: pom-xml-image template: metadata: labels: app: pom-xml-image spec: containers: - name: pom-xml-image image: pom-xml-image ? Do you want to write this configuration, along with the generated k8s manifests, to skaffold.yaml? Yes Generated manifest deployment.yaml was written Configuration skaffold.yaml was written You can now run [skaffold build] to build the artifacts or [skaffold run] to build and deploy or [skaffold dev] to enter development mode, with auto-redeploy
skaffold.yaml
は以下のようになります。
apiVersion: skaffold/v2beta18 kind: Config metadata: name: skaffold-sample build: artifacts: - image: pom-xml-image buildpacks: builder: gcr.io/buildpacks/builder:v1 deploy: kubectl: manifests: - deployment.yaml portForward: - resourceType: service resourceName: pom-xml-image port: 8080
上記のskaffold.yaml
ではビルドやデプロイ、ポートフォワードの設定が行われています。
その他、ここで使われていない項目や設定の説明はこちらをご覧ください。
次に進む前にイメージの名前がpom-xml-sample
だとあまりにもあまりになので以下のように書き換えておきます。
また、Buildkitを有効にしておきます。
apiVersion: skaffold/v2beta18 kind: Config metadata: name: skaffold-sample build: artifacts: - image: spring-app buildpacks: builder: gcr.io/buildpacks/builder:v1 deploy: kubectl: manifests: - deployment.yaml portForward: - resourceType: service resourceName: pom-xml-image port: 8080
作成されたk8sのマニフェストのpom-xml-sample
の部分もspring-app
に書き換えておきます。
apiVersion: v1 kind: Service metadata: name: spring-app labels: app: spring-app spec: ports: - port: 8080 protocol: TCP clusterIP: None selector: app: spring-app --- apiVersion: apps/v1 kind: Deployment metadata: name: spring-app labels: app: spring-app spec: replicas: 1 selector: matchLabels: app: spring-app template: metadata: labels: app: spring-app spec: containers: - name: spring-app image: spring-app
devモードをで開発を行なう
前述したとおり、Skaffoldはローカルでの開発に置いてソースコードの変更を検知して、自動でBuild、Push、Deployまでのサポート
までをサポートしてくれます。
devモードで起動するとその機能が利用可能で、以下のコマンドでdevモードで起動します。
$ skaffold dev (省略) Starting test... Tags used in deployment: - spring-app -> spring-app:e0b79f2a42356a8de0ba7b3da0f0f74903c0d9b99ddf1db39ed36a872a90d577 Starting deploy... - service/spring-app created - deployment.apps/spring-app created Waiting for deployments to stabilize... - deployment/spring-app is ready. Deployments stabilized in 1.129 second Press Ctrl+C to exit Watching for changes... [spring-app] [spring-app] . ____ _ __ _ _ [spring-app] /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ [spring-app] ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ [spring-app] \\/ ___)| |_)| | | | | || (_| | ) ) ) ) [spring-app] ' |____| .__|_| |_|_| |_\__, | / / / / [spring-app] =========|_|==============|___/=/_/_/_/ [spring-app] :: Spring Boot :: (v2.5.2) [spring-app] [spring-app] 2021-07-08 17:17:30.213 INFO 20 --- [ main] d.h.s.SkaffoldSampleApplication : Starting SkaffoldSampleApplication v0.0.1-SNAPSHOT using Java 11.0.11 on spring-app-6d5b5c74c4-2q6xs with PID 20 (/workspace/target/skaffold-sample-0.0.1-SNAPSHOT.jar started by cnb in /workspace) [spring-app] 2021-07-08 17:17:30.215 INFO 20 --- [ main] d.h.s.SkaffoldSampleApplication : No active profile set, falling back to default profiles: default [spring-app] 2021-07-08 17:17:31.073 INFO 20 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) [spring-app] 2021-07-08 17:17:31.085 INFO 20 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] [spring-app] 2021-07-08 17:17:31.086 INFO 20 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.48] [spring-app] 2021-07-08 17:17:31.156 INFO 20 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext [spring-app] 2021-07-08 17:17:31.157 INFO 20 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 880 ms [spring-app] 2021-07-08 17:17:31.683 INFO 20 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' [spring-app] 2021-07-08 17:17:31.691 INFO 20 --- [ main] d.h.s.SkaffoldSampleApplication : Started SkaffoldSampleApplication in 2.022 seconds (JVM running for 2.444)
コマンドを実行するとビルドが始まり少し待つとKubernetes上にデプロイされます。
また、devモードで起動するとローカルマシンへのポートフォワードも自動的に行ってくれます
ここまででServiceとDeploymentがローカルのMinikubeで作ったクラスタに作成されリソースが作られている状態でかつホストマシンへのポートフォワードまで行われています。
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d3h spring-app ClusterIP None <none> 8080/TCP 4m48s $ kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE spring-app 1/1 1 1 5m17s
8080
ポートフォワードされているのでcURLでアクセスしてみます。
$ curl localhost:8080 {"timestamp":"2021-07-08T17:28:57.961+00:00","status":404,"error":"Not Found","path":"/"}
現状はコントローラーを作成していないので404が返ってきます。
以下のクラスを作成してコントローラーを1つ作ってみます。
@RestController public class SampleController { @GetMapping("/") public String helle(){ return "Hello, Skaffold"; } }
コードを修正すると自動でビルドが走りクラスターにデプロイされます。
再度、cURLでアクセスすると今度はHello, Skaffold
の文字列が返ってきます。
$ curl localhost:8080 Hello, Skaffold
debugモードで起動して、IntelliJを用いてDebugする
Skaffoldのdebugモードはdevモードと同じように動作しますが、debug用のPodが立ち上がりlanguage runtimeに応じたdebug用のポートがホストにポートフォワードされます。
Javaの場合はJDWPを用いてdebugが可能となるようです。
ここで、debug自動デプロイ機能が無効になるので注意が必要です。
以下のコマンドでdebugモードで起動します。
$ skaffold debug (省略) [spring-app] Picked up JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,address=5005,suspend=n,quiet=y (省略) Port forwarding pod/spring-app-7b74d66d9-42wns in namespace default, remote port 5005 -> 127.0.0.1:5005
ログに出力されているように出力されJWDPのポートが5005で公開されてるのがわかります。
InteliJからリモートdebug用のプロセスに接続します。
Run/Debug Configurationを開き左上の+
ボタンからRemote JVM Debug
を選択します。
基本はデフォルトのままの設定で大丈夫ですが、名前の部分だけspring-app
にしておきます。
Apply
を押してIntelliJをDebug実行をすると起動します。
先程のコントローラーにブレークポイントを置いておきます。
この状態で再度cURLでリクエストを投げると置いたブレークポイントで停止することが確認できます。