Jenkins CI + K8s
CloudNet@ Gasida님이 진행하는 CI/CD + ArgoCD + Vault Study 를 진행하며 학습한 내용을 공유합니다.
이번 포스트에서는 Jenkins 란 무엇인지, Jenkins를 사용한 CI(Continuous Integration) 과정에 대해 다루겠습니다.
실습 환경은 Jenkins + ArgoCD 실습 환경 구축 의 실습환경을 사용합니다.
1. Jenkins 란?
Jenkins는 오픈소스 자동화 서버입니다. 프로젝트의 Build, Test, Deploy 및 자동화를 지원하는 수백 개의 플러그인을 제공하고, 각 파이프라인의 단계별 동작에 대한 내용을 코드로 정의하여 사용할 수 있습니다.
1.1. Jenkins 핵심 특징
Continuous Integration and Continuous Delivery
확장 가능한 자동화 서버로 단순 CI 서버로도, 어느 프로젝트든 CD 허브로도 활용할 수 있습니다.Easy installation
Windows, Linux, macOS 및 기타 Unix 계열 운영체제를 위한 패키지를 갖추고 있어 바로 실행 가능한 독립형 Java 기반 프로그램입니다.Easy configuration
웹 인터페이스로 손쉽게 설정 및 구성할 수 있으며, 즉석 오류 검사와 내장 도움말을 포함합니다.Plugins
업데이트 센터의 수백 개 플러그인으로 CI/CD 툴체인의 거의 모든 도구와 통합됩니다.Extensible
플러그인 아키텍처를 통해 다양한 기능으로 확장이 가능합니다.Distributed
여러 머신으로 작업을 분산해 다양한 플랫폼에서 Build, Test, Deploy를 더 빠르게 수행할 수 있습니다.
2. Jenkins Pipeline 이란?
Jenkins Pipeline(이하 “Pipeline”)은 Jenkins에서 지속적 통합(Continuous Integration) 과 지속적 전달(Continuous Delivery) 프로세스를 구현하기 위해 제공되는 플러그인 모음(Suite of Plugins) 입니다.
즉, 애플리케이션의 빌드(Build) -> 테스트(Test) -> 배포(Deploy) 과정을 코드 형태로 정의하고 자동화할 수 있는 도구 세트입니다.
2.1. Jenkins Pipeline 장점
- 코드 : 애플리케이션 CI/CD 프로세스를 코드 형식으로 작성할 수 있고, 해당 코드를 중앙 리포지터리에 저장하여 팀원과 공유 및 작업 가능
- 내구성 : 젠킨스 서비스가 의도적으로 또는 우발적으로 재시작되더라도 문제없이 유지됨
- 일시 중지 가능 : 파이프라인을 실행하는 도중 사람의 승인이나 입력을 기다리기 위해 중단하거나 기다리는 것이 가능
- 다양성 : 분기나 반복, 병렬 처리와 같은 다양한 CI/CD 요구 사항을 지원
2.2. Jenkins Pipeline 용어
- Pipeline : 전체 빌드 프로세스를 정의하는 코드.
- Node == Agent : 파이프라인을 실행하는 시스템
- stages : 순차 작업 명세인 stage 들의 묶음
- stage : 특정 단계에서 수행되는 작업들의 정의. (옵션) agents 설정
- steps : 파이프라인의 특정 단계에서 수행되는 단일 작업을 의미.
- post : 빌드 후 조치, 일반적으로 stages 작업이 끝난 후 추가적인 steps/step
- Directive : environment, parameters, triggers, input, when - Docs
- environment (key=value) : 파이프라인 내부에서 사용할 환경변수
- parameters : 입력 받아야할 변수를 정의 - Type(string, text, choice, password …)
- when : stage 를 실행 할 조건 설정
2.3. Jenkins Pipeline의 3가지 구성 형태
- Pipeline script : 일반적인 방식으로 Jenkins 파이프라인을 생성하여 Shell Script를 직접 생성하여 빌드하는 방식
- Through the classic UI - you can enter a basic Pipeline directly in Jenkins through the classic UI.
- Pipeline script from SCM : 사전 작성한 JenkinsFile을 형상관리 저장소에 보관하고, 빌드 시작 시 파이프라인 프로젝트에서 호출 실행하는 방식
- In SCM - you can write a
Jenkinsfilemanually, which you can commit to your project’s source control repository.
- In SCM - you can write a
- Blue Ocean 기반 : UI기반하여 시각적으로 파이프라인을 구성하면, JenkinsFile이 자동으로 생성되어 실행되는 방식
- Through Blue Ocean - after setting up a Pipeline project in Blue Ocean, the Blue Ocean UI helps you write your Pipeline’s
Jenkinsfileand commit it to source control.
- Through Blue Ocean - after setting up a Pipeline project in Blue Ocean, the Blue Ocean UI helps you write your Pipeline’s
2.4. 파이프라인 2가지 구문 : 선언형 파이프라인(권장)과 스크립트형 파이프라인
- 선언형 파이프라인 : 쉽게 작성 가능, 최근 문법이고 젠킨스에서 권장하는 방법, step 필수!
- 스크립트형 파이프라인 : 커스텀 작업에 용이, 복잡하여 난이도가 높음, step은 필수 아님
2.4.1. Declarative Pipeline 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pipeline {
agent any # Execute this Pipeline or any of its stages, on any available agent.
stages {
stage('Build') { # Defines the "Build" stage.
steps {
// # Perform some steps related to the "Build" stage.
}
}
stage('Test') {
steps {
//
}
}
stage('Deploy') {
steps {
//
}
}
}
}
2.4.2. Scripted Pipeline 예시
1
2
3
4
5
6
7
8
9
10
11
node { # Execute this Pipeline or any of its stages, on any available agent.
stage('Build') { # Defines the "Build" stage. stage blocks are optional in Scripted Pipeline syntax. However, implementing stage blocks in a Scripted Pipeline provides clearer visualization of each stage's subset of tasks/steps in the Jenkins UI.
// # Perform some steps related to the "Build" stage.
}
stage('Test') {
//
}
stage('Deploy') {
//
}
}
3. Jenkins CI + Kubernetes
3.1. Jenkins Plugin 설치 및 자격증명 설정
3.1.1. Jenkins Plugin 설치
Jenkins 관리 -> Plugins
Available plugins -> Search available plugins -> 아래 3개 플러그인 선택 -> Install
3.1.2. Jenkins Credential 설정
Jenkins 관리 -> Credentials -> System -> Global credentials -> Add Credentials
- Gogs Repo 자격증명 설정 : gogs-crd
- Kind : Username with password
- Username : devops
- Password : *
* - ID : gogs-crd
- 도커 허브 자격증명 설정 : dockerhub-crd
- Kind : Username with password
- Username : *<도커 계정명="">*도커>
- Password : *<도커 계정="" 암호="" 혹은="" 토큰="">*도커>
- ID : dockerhub-crd
3.2. Jenkins Item 생성(Pipeline)
Jenkins Home -> 새로운 Item(New Item) -> item type(Pipeline) item name:
pipeline-ci
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
pipeline {
agent any
environment {
DOCKER_IMAGE = '<자신의 도커 허브 계정>/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://<자신의 IP>:3000/devops/dev-app.git', // Git에서 코드 체크아웃
credentialsId: 'gogs-crd' // Credentials ID
}
}
stage('Read VERSION') {
steps {
script {
// VERSION 파일 읽기
def version = readFile('VERSION').trim()
echo "Version found: ${version}"
// 환경 변수 설정
env.DOCKER_TAG = version
}
}
}
stage('Docker Build and Push') {
steps {
script {
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
// DOCKER_TAG 사용
def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
appImage.push()
appImage.push("latest")
}
}
}
}
}
post {
success {
echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
}
failure {
echo "Pipeline failed. Please check the logs."
}
}
}
생성 후 지금 빌드 (클릭)
3.2.1. 결과 확인 1 (Jenkins Stage View)
3.2.2. 결과 확인 2 (Docker Hub)
3.3. Kubernetes 에서 위 컨테이너 이미지를 사용하는 Deployment 배포
Private Container Repository를 사용하는 경우 Repository에 대한 자격 증명이 없을 경우 아래와 같이
ErrImagePull문제가 발생
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 디플로이먼트 오브젝트 배포 : 리플리카(파드 2개), 컨테이너 이미지 >> 아래 도커 계정 부분만 변경해서 배포해보자
DHUSER=<도커 허브 계정명>
DHUSER=kkankkandev
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 2
selector:
matchLabels:
pod: timeserver-pod
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: docker.io/$DHUSER/dev-app:0.0.1
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 30
httpGet:
path: /healthz
port: 80
scheme: HTTP
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
EOF
watch -d kubectl get deploy,rs,pod -o wide
# 배포 상태 확인 : kube-ops-view 웹 확인
kubectl get events -w --sort-by '.lastTimestamp'
kubectl get deploy,pod -o wide
kubectl describe pod
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 53s default-scheduler Successfully assigned default/timeserver-7cf7db8f6c-mtvn7 to myk8s-worker
Normal BackOff 19s (x2 over 50s) kubelet Back-off pulling image "docker.io/gasida/dev-app:latest"
Warning Failed 19s (x2 over 50s) kubelet Error: ImagePullBackOff
Normal Pulling 4s (x3 over 53s) kubelet Pulling image "docker.io/gasida/dev-app:latest"
Warning Failed 3s (x3 over 51s) kubelet Failed to pull image "docker.io/gasida/dev-app:latest": failed to pull and unpack image "docker.io/gasida/dev-app:latest": failed to resolve reference "docker.io/gasida/dev-app:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
Warning Failed 3s (x3 over 51s) kubelet Error: ErrImagePull
3.4. Kubernetes 에서 위 컨테이너 이미지를 사용하는 Deployment 배포 - 문제해결
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# k8s secret : 도커 자격증명 설정
kubectl get secret -A # 생성 시 타입 지정
DHUSER=<도커 허브 계정>
DHPASS=<도커 허브 암호 혹은 토큰>
echo $DHUSER $DHPASS
kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username=$DHUSER \
--docker-password=$DHPASS
# 확인 : base64 인코딩 확인
kubectl get secret
kubectl get secrets -o yaml | kubectl neat
kubectl get secret dockerhub-secret -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq
# Deployment Object Upgrade: imagePullSecrets 추가
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 2
selector:
matchLabels:
pod: timeserver-pod
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: docker.io/$DHUSER/dev-app:0.0.1
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 30
httpGet:
path: /healthz
port: 80
scheme: HTTP
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
imagePullSecrets:
- name: dockerhub-secret
EOF
watch -d kubectl get deploy,rs,pod -o wide
# 확인
kubectl get events -w --sort-by '.lastTimestamp'
kubectl get deploy,pod
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/timeserver 2/2 2 2 8m39s
# NAME READY STATUS RESTARTS AGE
# pod/timeserver-7cb6bcdf75-8tnx9 1/1 Running 0 29s
# pod/timeserver-7cb6bcdf75-cmwxx 1/1 Running 0 90s
3.5. 배포한 Deployment 외부 노출 (Publish)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
##############################################################
# 서비스 생성
##############################################################
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: timeserver
spec:
selector:
pod: timeserver-pod
ports:
- port: 80
targetPort: 80
protocol: TCP
nodePort: 30000
type: NodePort
EOF
##############################################################
# 서비스 및 endpoint 확인
##############################################################
kubectl get service,ep timeserver -owide
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
# service/timeserver NodePort 10.96.142.254 <none> 80:30000/TCP 9s pod=timeserver-pod
# NAME ENDPOINTS AGE
# endpoints/timeserver 10.244.1.5:80,10.244.1.6:80 9s
##############################################################
# Service(NodePort)로 접속 확인 "노드IP:NodePort"
##############################################################
curl http://127.0.0.1:30000
# The time is 4:09:35 PM, VERSION 0.0.1
# Server hostname: timeserver-7cb6bcdf75-8tnx9
curl http://127.0.0.1:30000
# The time is 4:09:35 PM, VERSION 0.0.1
# Server hostname: timeserver-7cb6bcdf75-cmwxx
curl http://127.0.0.1:30000/healthz
# Healthy
# 반복 접속 해두기 : 부하분산 확인
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; sleep 1 ; done
for i in {1..100}; do curl -s http://127.0.0.1:30000 | grep name; done | sort | uniq -c | sort -nr
# 파드 복제복 증가 : service endpoint 대상에 자동 추가
kubectl scale deployment timeserver --replicas 4
kubectl get service,ep timeserver -owide
# 반복 접속 해두기 : 부하분산 확인
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; sleep 1 ; done
for i in {1..100}; do curl -s http://127.0.0.1:30000 | grep name; done | sort | uniq -c | sort -nr
3.6. 현재 Application Update (Kubernetes Deploying an application with Jenkins)
이번에는 현재 배포한 Sample Application을 새 버전(0.0.2) 태그로 컨테이너 이미지를 빌드 후 컨테이너 저장소(Docker Hub) Push -> Kubernetes Deployment 업데이트를 해보겠습니다. (수동)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
##############################################################
# gogs 컨테이너진입
##############################################################
sudo docker exec -it gogs bash
##############################################################
# 태그 변경
##############################################################
cd /data/dev-app
# 현재 버전 확인
grep -R "0.0.1" .
# ./VERSION:0.0.1
# ./server.py: response_string = now.strftime("The time is %-I:%M:%S %p, VERSION 0.0.1\n")
# VERSION 변경 : 0.0.2
# server.py 변경 : 0.0.2
sed -i 's/0\.0\.1/0\.0\.2/g' ./*
# 버전 변경 확인 및 git add commit push
grep -R "0.0.2" VERSION server.py
# ./VERSION:0.0.2
# ./server.py: response_string = now.strftime("The time is %-I:%M:%S %p, VERSION 0.0.2\n")
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
##############################################################
# Jenkins 접속 후 지금 빌드 실행 (수동)
##############################################################
# eth0번 IP 변수 저장
IP=$(ip addr show eth0 | grep 'inet ' | awk '{print $2}' | cut -d'/' -f1)
# Mac의 open 명령어와 비슷한 동작을 위한 alias 설정
alias open='powershell.exe -NoProfile -Command Start-Process'
# Jenkins 접속 -> 아까 생성한 pipeline-ci 에서 '지금 빌드' 실행
open http://$IP:8080
##############################################################
# Kubernetes Deployment 이미지 수정
##############################################################
# 반복 접속 해두기 : 부하분산 확인
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; sleep 1 ; done
for i in {1..100}; do curl -s http://127.0.0.1:30000 | grep name; done | sort | uniq -c | sort -nr
# 이미지 변경
kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:0.0.2 && watch -d "kubectl get deploy,ep timeserver -owide; echo; kubectl get rs,pod"
# 롤링 업데이트 확인
watch -d kubectl get deploy,rs,pod,svc,ep -owide
kubectl get deploy,rs,pod,svc,ep -owide
# kubectl get deploy $DEPLOYMENT_NAME
kubectl get deploy timeserver
kubectl get pods -l pod=timeserver-pod
# 변경 확인 (아까 실행한 명령어 0.0.1 -> 0.0.2)
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; sleep 1 ; done
# Server hostname: timeserver-7cb6bcdf75-8tnx9
# The time is 5:27:49 PM, VERSION 0.0.1
# Server hostname: timeserver-7cb6bcdf75-cmwxx
# The time is 5:27:50 PM, VERSION 0.0.1
# Server hostname: timeserver-7cb6bcdf75-8tnx9
# The time is 5:27:51 PM, VERSION 0.0.2
# Server hostname: timeserver-86c98b478c-xt5gt
# The time is 5:27:52 PM, VERSION 0.0.2
3.7. Gogs Webhooks 설정: Jenkins Job Trigger
gogs 의 /data/gogs/conf/app.ini 파일 수정 후 컨테이너 재기동 - issue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
##############################################################
# gogs 컨테이너 진입
##############################################################
sudo docker exec -it gogs bash
##############################################################
# /data/gogs/conf/app.ini 파일 편집
##############################################################
[security]
INSTALL_LOCK = true
SECRET_KEY = 8Idn7RM0joCQ5Qa
LOCAL_NETWORK_ALLOWLIST = 172.28.8.232 # 각자 자신의 IP 추가
##############################################################
# gogs 컨테이너 탈출
##############################################################
exit
##############################################################
# gogs 컨테이너 재가동
##############################################################
sudo docker compose restart gogs
Jenkins job Trigger - [dev-app] - Setting → Webhooks → Gogs 클릭
- Payload URL :
http://172.28.8.232:8080/gogs-webhook/?job=**SCM-Pipeline**/# 각자 자신의 IP - Content Type :
application/json - Secret :
asdasd123 - When should this webhook be triggered? : Just the push event
- Active : Check
=> Add webhook 클릭 -> Test Delivery 시도 시, 현재는 Jenkins 미설정 상태로 404 실패
Webhook 설정
생성한 Webhook 확인
3.8. Jenkins Item 생성(Pipeline Name - SCM-Pipeline)
- GitHub project :
http://***자신의 IP>***:3000/***<Gogs 계정명>***/dev-app← .git 은 제거- GitHub project :
http://172.28.8.232:3000/devops/dev-app
- GitHub project :
- Use Gogs secret : asdasd123
- Triggers : Build when a change is pushed to Gogs 체크
- Pipeline script from SCM
- SCM : Git
- Repo URL(
http://***자신의 IP***:3000/***<Gogs 계정명>***/dev-app) - Credentials(devops/***)
- Branch(*/main)
- Repo URL(
- Script Path : Jenkinsfile
- SCM : Git
3.9. Jenkinsfile 작성 후 git push
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
##############################################################
# gogs 컨테이너 진입
##############################################################
sudo docker exec -it gogs bash
##############################################################
# 태그 변경
##############################################################
cd /data/dev-app
# 현재 버전 확인
grep -R "0.0.2" VERSION server.py
# ./VERSION:0.0.2
# ./server.py: response_string = now.strftime("The time is %-I:%M:%S %p, VERSION 0.0.2\n")
# VERSION 변경 : 0.0.3
# server.py 변경 : 0.0.3
sed -i 's/0\.0\.2/0\.0\.3/g' VERSION server.py
# 버전 변경 확인 및 git add commit push
grep -R "0.0.3" VERSION server.py
# ./VERSION:0.0.3
# ./server.py: response_string = now.strftime("The time is %-I:%M:%S %p, VERSION 0.0.3\n")
##############################################################
# Jenkinsfile 생성
##############################################################
# Jenkinsfile
pipeline {
agent any
environment {
DOCKER_IMAGE = 'kkankkandev/dev-app'
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://172.28.8.232:3000/devops/dev-app.git',
credentialsId: 'gogs-crd'
}
}
stage('Read VERSION') {
steps {
script {
def version = readFile('VERSION').trim()
if (!version) { error 'VERSION is empty' }
env.DOCKER_TAG = version
echo "IMAGE=${env.DOCKER_IMAGE}:${env.DOCKER_TAG}"
}
}
}
stage('Docker Build and Push') {
steps {
script {
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
def imageRef = "${env.DOCKER_IMAGE}:${env.DOCKER_TAG}"
def appImage = docker.build(imageRef)
appImage.push()
appImage.push('latest')
}
}
}
}
}
post {
success { echo "Pushed ${env.DOCKER_IMAGE}:${env.DOCKER_TAG}" }
failure { echo 'Pipeline failed. Check logs.' }
}
}
##############################################################
# 변경된 내용 git push
##############################################################
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
3.10. WebHook 기록, Jenkins 트리거 빌드 확인 및 Container Image 확인
Gogs Webhook 기록
Jenkins Trigger Build
Docker Hub 0.0.3 버전 컨테이너 이미지 확인
3.11. Kubernetes 에 신규 버전 컨테이너 이미지 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DHUSER=<도커 허브 계정>
DHUSER=kkankkandev
# 신규 버전 적용
kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:0.0.3 && while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; sleep 1 ; done
# Server hostname: timeserver-86c98b478c-xt5gt
# The time is 6:29:39 PM, VERSION 0.0.2
# Server hostname: timeserver-86c98b478c-fqnrm
# The time is 6:29:40 PM, VERSION 0.0.2
# Server hostname: timeserver-86c98b478c-p9jfl
# The time is 6:29:41 PM, VERSION 0.0.3 # 0.0.3 으로 변경 확인
# Server hostname: timeserver-75d88bc86d-9fxxb
# The time is 6:29:42 PM, VERSION 0.0.3
# 확인
watch -d "kubectl get deploy,ep timeserver; echo; kubectl get rs,pod"
궁금하신 점이나 추가해야 할 부분은 댓글이나 아래의 링크를 통해 문의해주세요.
Written with KKamJi





















