git repo: https://github.com/jaekwonHa/practice-spring-on-k8s-workflow
kubernetes 환경에서 동작하는 Spring boot 어플리케이션을 개발할 때, 특히 kubernetes 를 이제 막 시작하는 경우 너무 많은 도구들이 존재해 뭐부터 사용해야 할 지 막막한 경우가 있습니다.
이 글에서는 intellij + spring boot + local kubernetes cluster 환경에서 내가 수정한 코드를 kubernetes 에 빠르게 배포, 테스트하고자 할 때 유용한 도구들에 대해서 소개해봅니다.
- jib
- docker-credential
- cloud code
- stern
- kustomize
- skafold
jib
jib 는 java 어플리케이션을 도커 혹은 OCI 이미지로 최적화해주는 maven, gradle plugin 입니다.
maven, gradle plugin 이라는 점에서 감이 오셨겠지만, 작성 중인 코드를 빌드 시에 도커 이미지로 만들어 주는 과정을 수행할 수 있고, docker hub 혹은 private registry 에 push 하는 것도 가능합니다. 이 과정에서 docker daemon 이 필요하지 않습니다.
https://cloudplatform.googleblog.com/2018/07/introducing-jib-build-java-docker-images-better.html
plugins {
id 'com.google.cloud.tools.jib' version '2.1.0'
}
build.gradle 에 위와 같이 추가해주면 아래 세가지 명령어를 추가로 사용할 수 있습니다.
- jib
- container image 를 빌드하고 원격 registry 에 이미지를 push 합니다.
- jibBuildTar
- container image 를 빌드하고, tar 형태로 압축합니다.
- jibDockerBuild
- local docker deamon 을 이용하여 container image 를 빌드합니다. local docker daemon 을 사용하기에 local 에만 image 가 올라갑니다.
> docker history myapp:0.0.1-SNAPSHOT
IMAGE CREATED CREATED BY SIZE COMMENT
c586959dbfe3 50 years ago jib-gradle-plugin:2.1.0 1.41kB classes
<missing> 50 years ago jib-gradle-plugin:2.1.0 1B resources
<missing> 50 years ago jib-gradle-plugin:2.1.0 18.3MB dependencies
<missing> 50 years ago bazel build ... 100MB
<missing> 50 years ago bazel build ... 8.41MB
<missing> 50 years ago bazel build ... 1.93MB
<missing> 50 years ago bazel build ... 15.1MB
<missing> 50 years ago bazel build ... 1.79MB
docker image layer 를 보면 class, resources, dependencies 로 layer 가 나뉘어 어느정도 최적화됨을 알 수 있습니다.
docker-credential
jib 를 사용하여 원격 registry 에 이미지를 push 하려다보면 당연히 registry 에 대한 인증이 필요합니다.
docker-credential 은 docker login
명령어로 생성할 수 있지만 jib 에서 docker hub 로 이미지를 올려주기 위해서는 아래와 같은 명령어가 필요합니다.
> docker login registry.hub.docker.com -u {{ USER NAME }} -p {{ PASSWORD }}
> docker-credential-desktop list
> docker-credential-osxkeychain list
이렇게 생긴 docker-credential 중 특정하게 사용하고 싶은게 있다면 build.gradle 에 jib.to.credHelper 를 선언해주면 편리합니다.
jib {
from {
image = 'openjdk:alpine'
}
to {
image = 'localhost:5000/my-image/built-with-jib'
credHelper = 'osxkeychain'
tags = ['tag2', 'latest']
}
container {
jvmFlags = ['-Dmy.property=example.value', '-Xms512m', '-Xdebug']
mainClass = 'mypackage.MyApp'
args = ['some', 'args']
ports = ['1000', '2000-2003/udp']
labels = [key1:'value1', key2:'value2']
format = 'OCI'
}
}
Cloud Code
Cloud Code 란 intellij, vs code 에서 사용할 수 있는 kubernetes 파일 작성, 배포, 모니터링을 도와주는 ide plugin 입니다.
intellij 로 clode code plugin 을 설치하게 되면 다양한 기능들을 사용할 수 있습니다.
yaml 파일 작성 시 자동완성
위 스크린샷은 deployment.yaml 파일 작성 시 자동완성 해주는 부분인데, 옵션이 다양하다보니 딱 원하는 대로 생성해주진 않고, 생성 후에 수정이 필요합니다.
Kubernetes Cluster Monitoring
kubeconfig 파일을 불러와 cluster 의 전체적인 형상을 보여주는 화면입니다. 위 화면은 로컬에서 만들어진 kubernetes cluster 입니다.
Run Configuration
gradle + spring boot 어플리케이션을 Run/Debug 할때 ./gradlew bootRun -Dspring.profiles.active=dev
와 같은 명령어를 사용하듯, kubernetes + spring boot 어플리케이션을 Run/Debug 할 수 있는 Run Configuration 입니다.
소스 코드가 변경되었을때 자동으로 re-deploy 해준다거나, 특정 파라미터를 주어 실행시킨다거나, 브레이킹 포인트를 걸어 디버깅할 수도 있습니다.
우리가 gradlew 명령어를 사용할 수 있듯이, 이런 기능은 skafold 명령어를 사용해서 직접 실행할 수도 있습니다.
stern
cluster 에 여러개의 pod를 띄웠을때, 로그를 보기 위해서는 각 pod에 접근해서 로그를 보거나, 터미널을 화면 분할하여 모든 pod에 tail을 걸어둬야 할 겁니다.
stern 명령어는 정규표현식으로 작성된 쿼리에 대해 일치하는 name
의 pod 로그를 모두 출력해줍니다.
> stern myapp
+ myapp-55c6d5c79f-p2cjk › myapp
myapp-55c6d5c79f-p2cjk myapp
myapp-55c6d5c79f-p2cjk myapp . ____ _ __ _ _
myapp-55c6d5c79f-p2cjk myapp /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
myapp-55c6d5c79f-p2cjk myapp ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
myapp-55c6d5c79f-p2cjk myapp \\/ ___)| |_)| | | | | || (_| | ) ) ) )
myapp-55c6d5c79f-p2cjk myapp ' |____| .__|_| |_|_| |_\__, | / / / /
myapp-55c6d5c79f-p2cjk myapp =========|_|==============|___/=/_/_/_/
myapp-55c6d5c79f-p2cjk myapp :: Spring Boot :: (v2.3.3.RELEASE)
myapp-55c6d5c79f-p2cjk myapp
...
kustomize
kustomize 란 쿠버네티스 구성을 환경에 따라 사용자가 원하는대로 설정을 변경하고, 기존코드의 재사용성을 높일 수 있는 도구입니다.
예를 들면 아래와 같이 deployment, service 구성을 가지고 있는 클러스터가 있을 때
- 환경별로 쿠버네티스 리소스의 이름 앞에 붙여줄 prefix 를 다르게 한다던가
- spring boot 어플리케이션 구동 시 profiles 을 다르게 한다던가
- pod 개수를 다르게 유지한다던가
하는 등의 구체적인 설정을 다르게 주어야 하는 니즈가 있을 수 있습니다.
이런 경우에 kustomize 를 사용한다면, base/ 하위 코드로 쿠버네티스를 구성하고, 환경별로 설정을 나누어 관리할 수 있습니다.
build 명령어를 수행해보면 아래와 같이 완성된 리소스들의 yaml 파일이 출력되고, 이를 바로 실행하게 되면 클러스터 구성이 시작됩니다.
> kustomize build overlays/production
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: myapp
name: myapp
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: myapp
status:
loadBalancer: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: myapp
name: myapp
spec:
replicas: 5
selector:
matchLabels:
app: myapp
strategy: {}
template:
metadata:
labels:
app: myapp
spec:
containers:
- env:
- name: spring.profiles.active
value: production
image: hazxz/myapp
name: myapp
resources: {}
status: {}
> kustomize build overlays/production | k apply -f -
skafold
skafold 란 쿠버네티스 기반에서의 개발, 테스트, 디버깅을 도와주는 도구입니다.
개발 도중 수정된 코드를 쿠버네티스에 반영하기 위해서는, 컴파일-컨테이너 이미지 빌드-태그 추가-변경된 이미지 반영...등 반복적인 작업이 필요합니다.
이런 과정을 자동화해주어 수정된 코드를 바로 쿠버네티스 환경에서 테스트 해볼 수 있게 해줍니다.
만약 kustomize 와 같이 사용한다면 아래와 같은 skaffold.yaml 파일을 만들어서 쿠버네티스 환경에서 빠르게 개발,테스트 할 수 있습니다.
각 파일들의 내용은 블로그 상단의 git repo 에서 참고하실 수 있습니다.
apiVersion: skaffold/v2beta7
kind: Config
metadata:
name: myapp
build:
artifacts:
- image: hazxz/myapp
jib: {}
deploy:
kustomize:
paths:
- k8s/base
profiles:
- name: development
deploy:
kustomize:
paths:
- k8s/overlays/development
- name: production
deploy:
kustomize:
paths:
- k8s/overlays/production
> skaffold init --skip-build
> skaffold dev --port-forward=true --profile=production
Listing files to watch...
- hazxz/myapp
Generating tags...
- hazxz/myapp -> hazxz/myapp:71c040e
Checking cache...
- hazxz/myapp: Found. Tagging
Tags used in deployment:
- hazxz/myapp -> hazxz/myapp:d3a0814108063745f985eb32ef82d5e4569e8999bf7fe1a5de6e6af1a67abf0d
Starting deploy...
- service/myapp created
- deployment.apps/myapp created
Waiting for deployments to stabilize...
- deployment/myapp is ready.
Deployments stabilized in 3.827892089s
Port forwarding service/myapp in namespace default, remote port 80 -> address 127.0.0.1 port 4503
Press Ctrl+C to exit
Watching for changes...
[myapp]
[myapp] . ____ _ __ _ _
[myapp] /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
[myapp] ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
[myapp] \\/ ___)| |_)| | | | | || (_| | ) ) ) )
[myapp] ' |____| .__|_| |_|_| |_\__, | / / / /
[myapp] =========|_|==============|___/=/_/_/_/
[myapp] :: Spring Boot :: (v2.3.3.RELEASE)
[myapp]
[myapp]
[myapp] . ____ _ __ _ _
[myapp] /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
[myapp] ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
[myapp] \\/ ___)| |_)| | | | | || (_| | ) ) ) )
[myapp] ' |____| .__|_| |_|_| |_\__, | / / / /
[myapp] =========|_|==============|___/=/_/_/_/
[myapp] :: Spring Boot :: (v2.3.3.RELEASE)
[myapp]
[myapp]
[myapp] . ____ _ __ _ _
[myapp] /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
[myapp] ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
[myapp] \\/ ___)| |_)| | | | | || (_| | ) ) ) )
[myapp] ' |____| .__|_| |_|_| |_\__, | / / / /
[myapp] =========|_|==============|___/=/_/_/_/
[myapp] :: Spring Boot :: (v2.3.3.RELEASE)
...
...
production 구성으로 띄워서 총 5개의 파드가 생성되어 로그가 중복되어 보입니다.
skaffold dev --port-forward=true --profile=production
명령어 수행시에 코드를 수정하면 다시 빌드, 배포가 이뤄지는 것을 볼 수 있고, port-forward true 로 주었기 때문에, 로그 중에 port 4503
라는 부분을 참고하여 해당 포트로 API 를 호출해볼 수도 있습니다.
또한 직접 명령어를 수행할 수도 있지만, Cloud Code 의 Run Configuration 을 이용하여 IDE 를 통해 실행하는 것도 가능합니다.