Ssoon
3주차 - Jenkins CI/ArgoCD + K8S : Jenkins CI + K8S(Kind) 본문
CloudNet@ 가시다님이 진행하는 CI/CD 맛보기 스터디
✅ Jenkins CI + K8S(Kind)
🧿 kind 소개
Kind는 Kubernetes IN Docker의 약자로, Kubernetes 클러스터를 로컬 환경에서 Docker 컨테이너로 실행할 수 있게 해주는 도구입니다.
Kind의 주요 특징
- 로컬 Kubernetes 클러스터:
- Kind는 로컬 개발 환경에서 Kubernetes 클러스터를 쉽게 만들 수 있도록 도와줍니다.
- 클러스터는 Docker 컨테이너로 실행되며, 여러 노드를 가질 수 있어 실제 Kubernetes 환경처럼 테스트할 수 있습니다.
- 빠르고 간편한 설정:
- 복잡한 설정 없이 간단한 명령어로 Kubernetes 클러스터를 생성하고, 클러스터에서 애플리케이션을 실행하고 테스트할 수 있습니다.
- 다중 노드 클러스터 지원:
- 컨트롤 플레인(관리 노드)과 워커 노드(작업을 실행하는 노드)를 모두 생성할 수 있습니다.
- 개발 환경에서 다중 노드 클러스터를 구현할 수 있어, 실제 Kubernetes 환경과 유사하게 테스트할 수 있습니다.
- 개발 및 테스트 용도:
- 로컬 환경에서 Kubernetes 클러스터를 테스트하거나 애플리케이션을 개발할 때 유용합니다.
- 클라우드 환경에 배포하기 전에 로컬에서 실험할 수 있는 안전한 공간을 제공합니다.
🧿 Kind 및 Tool 설치
- kind를 설치합니다.
[ssoon@localhost dev-app]$ [ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.25.0/kind-linux-amd64
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 97 100 97 0 0 127 0 --:--:-- --:--:-- --:--:-- 127
0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0
100 9697k 100 9697k 0 0 3780k 0 0:00:02 0:00:02 --:--:-- 20.8M
[ssoon@localhost dev-app]$ chmod +x ./kind
[ssoon@localhost dev-app]$ sudo mv ./kind /usr/local/bin/kind
- kubectl를 설치합니다.
[ssoon@localhost dev-app]$ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 138 100 138 0 0 389 0 --:--:-- --:--:-- --:--:-- 388
100 54.6M 100 54.6M 0 0 26.6M 0 0:00:02 0:00:02 --:--:-- 34.6M
[ssoon@localhost dev-app]$ sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
[ssoon@localhost dev-app]$ kubectl version --client
Client Version: v1.32.0
Kustomize Version: v5.5.
- Helm을 설치합니다.
[ssoon@localhost dev-app]$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
[ssoon@localhost dev-app]$ chmod 700 get_helm.sh
[ssoon@localhost dev-app]$ ./get_helm.sh
Downloading https://get.helm.sh/helm-v3.16.3-linux-amd64.tar.gz
Verifying checksum... Done.
Preparing to install helm into /usr/local/bin
helm installed into /usr/local/bin/helm
🧿 kind 로 k8s 배포
- kind: Cluster
- Kind 클러스터의 정의를 나타냅니다.
- apiVersion: kind.x-k8s.io/v1alpha4
- Kind 클러스터 설정의 API 버전을 지정합니다.
- networking.apiServerAddress: "192.168.56.105"
- Kubernetes API 서버가 외부에서 접근 가능한 호스트 주소를 설정합니다.
- 클러스터 외부에서 API 서버에 접속할 때 사용합니다.
- nodes : 클러스터의 노드 구성입니다.
- role: control-plane:
- Kubernetes의 제어(관리) 노드.
- API 서버 및 클러스터 관리 기능을 수행.
- extraPortMappings:
- 컨트롤 플레인 노드에서 포트를 매핑.
- 예: 클러스터 내부의 30000~30003 포트를 호스트의 동일한 포트로 매핑.
- role: worker:
- 워커 노드 2개를 추가. 워커 노드는 애플리케이션을 실행합니다.
- role: control-plane:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
apiServerAddress: "192.168.56.105"
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
- containerPort: 30003
hostPort: 30003
- role: worker
- role: worker
- Kind를 사용하여 "myk8s"라는 이름의 Kubernetes 클러스터를 생성합니다.
[ssoon@localhost dev-app]$ kind create cluster --config kind-3node.yaml --name myk8s
Creating cluster "myk8s" ...
✓ Ensuring node image (kindest/node:v1.31.2) 🖼
✓ Preparing nodes 📦 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-myk8s"
You can now use your cluster with:
kubectl cluster-info --context kind-myk8s
Thanks for using kind! 😊
- Kubernetes 클러스터에서 컨트롤 플레인 노드와 워커 노드가 모두 준비 완료 상태입니다.
- Docker에서는 각 노드에 해당하는 컨테이너가 실행 중이며, 컨트롤 플레인 노드는 외부에서 접근할 수 있도록 포트를 매핑하고 있습니다.
[ssoon@localhost dev-app]$ kubectl get node
NAME STATUS ROLES AGE VERSION
myk8s-control-plane Ready control-plane 41s v1.31.2
myk8s-worker Ready <none> 26s v1.31.2
myk8s-worker2 Ready <none> 26s v1.31.2
[ssoon@localhost dev-app]$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3e626902fc54 kindest/node:v1.31.2 "/usr/local/bin/entr…" About a minute ago Up About a minute 0.0.0.0:30000-30003->30000-30003/tcp, 192.168.56.105:34449->6443/tcp myk8s-control-plane
4609a420a1c1 kindest/node:v1.31.2 "/usr/local/bin/entr…" About a minute ago Up About a minute myk8s-worker
eba12f812ced kindest/node:v1.31.2 "/usr/local/bin/entr…" About a minute ago Up About a minute myk8s-worker2
- kube-ops-view 애플리케이션을 Kubernetes 클러스터에 성공적으로 배포하였고, 30001 포트로 외부에서 접근할 수 있습니다.
- kubectl 명령어로 배포된 상태와 서비스, Pod 정보를 확인한 결과, 모든 리소스가 정상적으로 실행되고 있음을 알 수 있습니다.
[ssoon@localhost dev-app]$ helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
"geek-cookbook" has been added to your repositories
[ssoon@localhost dev-app]$ helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30001 --set env.TZ="Asia/Seoul" --namespace kube-system
NAME: kube-ops-view
LAST DEPLOYED: Mon Dec 16 22:15:11 2024
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
export NODE_PORT=$(kubectl get --namespace kube-system -o jsonpath="{.spec.ports[0].nodePort}" services kube-ops-view)
export NODE_IP=$(kubectl get nodes --namespace kube-system -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
[ssoon@localhost dev-app]$ kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kube-ops-view 1/1 1 1 16s
NAME READY STATUS RESTARTS AGE
pod/kube-ops-view-657dbc6cd8-8kldm 1/1 Running 0 16s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kube-ops-view NodePort 10.96.252.164 <none> 8080:30001/TCP 16s
NAME ENDPOINTS AGE
endpoints/kube-ops-view 10.244.1.2:8080 16s
- http://127.0.0.1:30001/#scale=2
🧿 Jenkins 설정 : Plugin 설치, 자격증명 설정
- Jenkins Plugin 설치
- Gogs Repo 자격증명 설정 : gogs-crd
- Password : <Gogs 토큰>
- 도커 허브 자격증명 설정 : dockerhub-crd
- Password : <도커 토큰>
- k8s(kind) 자격증명 설정 : k8s-crd
- File : <kubeconfig 파일 업로드>
🧿 Jenkins Item 생성(Pipeline) : item name(pipeline-ci)
- Pipeline script
Pipeline 개요
- agent any: 어떤 Jenkins 에이전트에서도 실행될 수 있다는 의미입니다.
- environment: 전역 환경 변수 설정. 여기서는 DOCKER_IMAGE라는 변수를 정의하여 Docker 이미지 이름을 지정합니다.
1. Checkout 단계
- Git 저장소(http://192.168.56.105:3000/devops/dev-app.git)에서 main 브랜치를 체크아웃합니다. => Git에서 main이라는 기본 브랜치로 전환하여, 그 브랜치에서 작업을 시작
- credentialsId: 'gogs-crd'는 인증 정보를 사용하는 부분으로, 저장소에 접근하기 위한 사용자 이름과 비밀번호 또는 토큰을 설정합니다.
2. Read VERSION 단계
- VERSION 파일을 읽고 해당 내용을 DOCKER_TAG 환경 변수에 저장합니다.
- echo 명령을 사용하여 읽은 버전 정보를 출력합니다.
3. Docker Build and Push 단계
- Docker 이미지 빌드 및 푸시 단계입니다.
- docker.withRegistry(...) 블록 내에서 Docker Hub 레지스트리를 사용하여 이미지를 빌드하고 푸시합니다.
- ${DOCKER_IMAGE}:${DOCKER_TAG}로 이미지를 빌드하고, latest 태그로도 이미지를 푸시합니다.
4. post 블록
- 성공 시: Docker 이미지가 성공적으로 빌드되고 푸시되었을 때 메시지를 출력합니다.
- 실패 시: 파이프라인이 실패하면 로그를 확인하라는 메시지를 출력합니다.
pipeline {
agent any
environment {
DOCKER_IMAGE = 'kschoi728/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.56.105: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."
}
}
}
- 콘솔 Output 확인
Started by user admin
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/pipeline-ci
[Pipeline] {
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Checkout)
[Pipeline] git
The recommended git tool is: NONE
using credential gogs-crd
Cloning the remote Git repository
Cloning repository http://192.168.56.105:3000/devops/dev-app.git
> git init /var/jenkins_home/workspace/pipeline-ci # timeout=10
Fetching upstream changes from http://192.168.56.105:3000/devops/dev-app.git
> git --version # timeout=10
> git --version # 'git version 2.39.5'
using GIT_ASKPASS to set credentials
> git fetch --tags --force --progress -- http://192.168.56.105:3000/devops/dev-app.git +refs/heads/*:refs/remotes/origin/* # timeout=10
> git config remote.origin.url http://192.168.56.105:3000/devops/dev-app.git # timeout=10
> git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
Avoid second fetch
> git rev-parse refs/remotes/origin/main^{commit} # timeout=10
Checking out Revision d89b73e4b8a27dfa7e4d615197bad18aa1a2ca97 (refs/remotes/origin/main)
> git config core.sparsecheckout # timeout=10
> git checkout -f d89b73e4b8a27dfa7e4d615197bad18aa1a2ca97 # timeout=10
> git branch -a -v --no-abbrev # timeout=10
> git checkout -b main d89b73e4b8a27dfa7e4d615197bad18aa1a2ca97 # timeout=10
Commit message: "Add dev-app"
First time build. Skipping changelog.
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Read VERSION)
[Pipeline] script
[Pipeline] {
[Pipeline] readFile
[Pipeline] echo
Version found: 0.0.1
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Docker Build and Push)
[Pipeline] script
[Pipeline] {
[Pipeline] withEnv
[Pipeline] {
[Pipeline] withDockerRegistry
$ docker login -u kschoi728 -p ******** https://index.docker.io/v1/
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /var/jenkins_home/workspace/pipeline-ci@tmp/9c49f2a3-7a1f-42d7-abb7-683428ed4415/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
Login Succeeded
[Pipeline] {
[Pipeline] isUnix
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ docker build -t kschoi728/dev-app:0.0.1 .
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 187B done
#1 WARN: LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 2)
#1 WARN: JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals (line 5)
#1 DONE 0.0s
#2 [internal] load metadata for docker.io/library/python:3.12
#2 DONE 0.0s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.0s
#4 [internal] load build context
#4 transferring context: 36.06kB 0.0s done
#4 DONE 0.1s
#5 [1/3] FROM docker.io/library/python:3.12
#5 CACHED
#6 [2/3] COPY . /app
#6 DONE 0.1s
#7 [3/3] WORKDIR /app
#7 DONE 0.0s
#8 exporting to image
#8 exporting layers 0.1s done
#8 writing image sha256:39fb3b2eaf6511819071e4a0cb053863b5c69d7d49071fe20a448e114e02df23 done
#8 naming to docker.io/kschoi728/dev-app:0.0.1 done
#8 DONE 0.1s
[33m2 warnings found (use docker --debug to expand):
[0m - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 2)
- JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals (line 5)
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] isUnix
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ docker tag kschoi728/dev-app:0.0.1 index.docker.io/kschoi728/dev-app:0.0.1
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] isUnix
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ docker push index.docker.io/kschoi728/dev-app:0.0.1
The push refers to repository [docker.io/kschoi728/dev-app]
5f70bf18a086: Preparing
...
b6ca42156b9f: Layer already exists
21b82d5020dc: Pushed
0.0.1: digest: sha256:d6b8c69f01491056e37b1ffb2a7c50f9a44a4674a1e7f56a3832763a895a3ccb size: 2211
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] isUnix
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ docker tag kschoi728/dev-app:0.0.1 index.docker.io/kschoi728/dev-app:latest
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] isUnix
[Pipeline] withEnv
[Pipeline] {
[Pipeline] sh
+ docker push index.docker.io/kschoi728/dev-app:latest
The push refers to repository [docker.io/kschoi728/dev-app]
5f70bf18a086: Preparing
...
24b5ce0f1e07: Layer already exists
latest: digest: sha256:d6b8c69f01491056e37b1ffb2a7c50f9a44a4674a1e7f56a3832763a895a3ccb size: 2211
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // withDockerRegistry
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Declarative: Post Actions)
[Pipeline] echo
Docker image kschoi728/dev-app:0.0.1 has been built and pushed successfully!
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
- 도커 허브 확인
🧿 k8s 애플리케이션 배포
- timeserver라는 이름의 Deployment가 생성합니다.
- Deployment는 2개의 Pod를 생성하고, 각 Pod는 docker.io/kschoi728/dev-app:0.0.1 이미지를 사용하여 실행됩니다.
[ssoon@localhost dev-app]$ DHUSER=kschoi728
[ssoon@localhost dev-app]$ echo $DHUSER
kschoi728
[ssoon@localhost dev-app]$ 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
EOF
deployment.apps/timeserver created
- ImagePullBackOff: Kubernetes는 이미지를 가져오는 데 실패했으며, 그로 인해 이미지 풀을 다시 시도하려고 했지만 계속 실패한 상태입니다.
- Failed to pull image "docker.io/kschoi728/dev-app:0.0.1": Docker 이미지를 가져오는 데 실패했다는 의미입니다.
- pull access denied, repository does not exist or may require authorization: 이 메시지는 두 가지 주요 원인이 있을 수 있음을 나타냅니다:
- 이미지가 존재하지 않음: Docker Hub에 kschoi728/dev-app:0.0.1 이미지가 존재하지 않는 경우.
- 인증 문제: 이미지에 접근하기 위해 인증이 필요하지만, 해당 인증이 없거나 잘못된 경우.
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 52s default-scheduler Successfully assigned default/timeserver-6574df9fcc-8dgqs to myk8s-worker2
Normal BackOff 22s (x2 over 50s) kubelet Back-off pulling image "docker.io/kschoi728/dev-app:0.0.1"
Warning Failed 22s (x2 over 50s) kubelet Error: ImagePullBackOff
Normal Pulling 11s (x3 over 52s) kubelet Pulling image "docker.io/kschoi728/dev-app:0.0.1"
Warning Failed 9s (x3 over 50s) kubelet Failed to pull image "docker.io/kschoi728/dev-app:0.0.1": failed to pull and unpack image "docker.io/kschoi728/dev-app:0.0.1": failed to resolve reference "docker.io/kschoi728/dev-app:0.0.1": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
Warning Failed 9s (x3 over 50s) kubelet Error: ErrImagePull
- 도커 자격증명 설정
- Docker Hub에 저장된 이미지를 Kubernetes 클러스터에서 사용할 수 있도록 인증 정보를 설정합니다.
- dockerhub-secret라는 이름의 Docker registry secret을 생성합니다.
- 이 secret은 Docker Hub에 접근하기 위한 인증 정보를 포함합니다.
- --docker-server=https://index.docker.io/v1/는 Docker Hub의 서버 URL입니다.
- --docker-username=$DHUSER는 Docker Hub 사용자 이름입니다.
- --docker-password=$DHPASS는 Docker Hub의 패스워드 또는 개인 액세스 토큰입니다.
[ssoon@localhost dev-app]$ DHUSER=kschoi728
[ssoon@localhost dev-app]$ DHPASS=dckr_pat_qAvtuYOW9U4OEDevuW81CuluDk0
[ssoon@localhost dev-app]$ echo $DHUSER $DHPASS
kschoi728 dckr_pat_qAvtuYOW9U4OEDevuW81CuluDk0
[ssoon@localhost dev-app]$ kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username=$DHUSER \
--docker-password=$DHPASS
secret/dockerhub-secret created
[ssoon@localhost dev-app]$ kubectl get secret
NAME TYPE DATA AGE
dockerhub-secret kubernetes.io/dockerconfigjson 1 5s
[ssoon@localhost dev-app]$ kubectl describe secret
Name: dockerhub-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: kubernetes.io/dockerconfigjson
Data
====
.dockerconfigjson: 190 bytes
- 처음에 Pod들이 이미지 풀링 실패로 인해 ImagePullBackOff 상태였으나, dockerhub-secret을 이용하여 Docker Hub에서 이미지를 정상적으로 풀 수 있도록 설정한 후, Deployment와 Pods가 정상적으로 실행되었습니다.
- 이제 timeserver Deployment는 두 개의 Pod가 Running 상태로 작동하고 있습니다.
[ssoon@localhost dev-app]$ 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
imagePullSecrets:
- name: dockerhub-secret
EOF
deployment.apps/timeserver configured
[ssoon@localhost dev-app]$ kubectl get deploy,pod
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/timeserver 0/2 1 0 5m58s
NAME READY STATUS RESTARTS AGE
pod/timeserver-6574df9fcc-78br9 0/1 ImagePullBackOff 0 5m57s
pod/timeserver-6574df9fcc-8dgqs 0/1 ImagePullBackOff 0 5m57s
pod/timeserver-7fcbc89ff9-9tzqw 0/1 ContainerCreating 0 9s
[ssoon@localhost dev-app]$ kubectl get deploy,pod
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/timeserver 2/2 2 2 7m2s
NAME READY STATUS RESTARTS AGE
pod/timeserver-7fcbc89ff9-9tzqw 1/1 Running 0 73s
pod/timeserver-7fcbc89ff9-tsbv5 1/1 Running 0 40s
- curl-pod라는 이름의 새로운 Pod를 생성하고 상태를 확인합니다.
[ssoon@localhost dev-app]$ kubectl run curl-pod --image=curlimages/curl:latest --command -- sh -c "while true; do sleep 3600; done"
[ssoon@localhost dev-app]$ kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
curl-pod 1/1 Running 0 9s 10.244.2.4 myk8s-worker <none> <none>
timeserver-7fcbc89ff9-9tzqw 1/1 Running 0 111s 10.244.2.3 myk8s-worker <none> <none>
timeserver-7fcbc89ff9-tsbv5 1/1 Running 0 78s 10.244.1.4 myk8s-worker2 <none> <none>
- curl-pod는 timeserver Pod에 HTTP 요청을 보내어 서버의 시간과 버전 정보를 받아왔습니다.
[ssoon@localhost dev-app]$ PODIP1=10.244.2.3
[ssoon@localhost dev-app]$ kubectl exec -it curl-pod -- curl $PODIP1
The time is 1:51:22 PM, VERSION 0.0.1
Server hostname: timeserver-7fcbc89ff9-9tzqw
- 파드 1개 삭제 후 동작 확인 : 셀프 힐링 , 파드 IP 변경 -> 고정 진입점(고정 IP/도메인네임) 필요 => Service
[ssoon@localhost dev-app]$ POD1NAME=timeserver-7fcbc89ff9-9tzqw
[ssoon@localhost dev-app]$ kubectl delete pod $POD1NAME && kubectl get pod -w
pod "timeserver-7fcbc89ff9-9tzqw" deleted
NAME READY STATUS RESTARTS AGE
curl-pod 1/1 Running 0 2m24s
timeserver-7fcbc89ff9-pxlzz 1/1 Running 0 31s
timeserver-7fcbc89ff9-tsbv5 1/1 Running 0 3m33s
[ssoon@localhost dev-app]$ kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
curl-pod 1/1 Running 0 2m51s 10.244.2.4 myk8s-worker <none> <none>
timeserver-7fcbc89ff9-pxlzz 1/1 Running 0 58s 10.244.2.5 myk8s-worker <none> <none>
timeserver-7fcbc89ff9-tsbv5 1/1 Running 0 4m 10.244.1.4 myk8s-worker2 <none> <none>
- timeserver 서비스가 생성되었으며, 외부에서 30000 포트를 통해 이 서비스에 접근할 수 있습니다.
- 이 서비스는 timeserver-pod 라벨을 가진 Pod들에 트래픽을 전달합니다.
- 현재 연결된 두 개의 Pod(IP: 10.244.1.4, 10.244.2.5)에 서비스가 라우팅되고 있습니다.
[ssoon@localhost dev-app]$ 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
service/timeserver created
[ssoon@localhost dev-app]$ kubectl get service,ep timeserver -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/timeserver NodePort 10.96.47.27 <none> 80:30000/TCP 5s pod=timeserver-pod
NAME ENDPOINTS AGE
endpoints/timeserver 10.244.1.4:80,10.244.2.5:80 5s
- Service(ClusterIP)로 접속 확인 : 도메인네임, ClusterIP
[ssoon@localhost dev-app]$ kubectl exec -it curl-pod -- curl timeserver
The time is 1:55:18 PM, VERSION 0.0.1
Server hostname: timeserver-7fcbc89ff9-tsbv5
[ssoon@localhost dev-app]$ kubectl exec -it curl-pod -- curl $(kubectl get svc timeserver -o jsonpath={.spec.clusterIP})
The time is 1:55:26 PM, VERSION 0.0.1
Server hostname: timeserver-7fcbc89ff9-pxlzz
- Service(NodePort)로 접속 확인 "노드IP:NodePort"
[ssoon@localhost dev-app]$ curl http://127.0.0.1:30000
The time is 1:56:35 PM, VERSION 0.0.1
Server hostname: timeserver-7fcbc89ff9-tsbv5
- 반복 접속 해두기 : 부하분산 확인
[ssoon@localhost dev-app]$ while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 | grep name ; sleep 1 ; done
Server hostname: timeserver-7fcbc89ff9-tsbv5
Server hostname: timeserver-7fcbc89ff9-pxlzz
Server hostname: timeserver-7fcbc89ff9-tsbv5
Server hostname: timeserver-7fcbc89ff9-tsbv5
Server hostname: timeserver-7fcbc89ff9-pxlzz
Server hostname: timeserver-7fcbc89ff9-pxlzz
[ssoon@localhost dev-app]$ for i in {1..100}; do curl -s http://127.0.0.1:30000 | grep name; done | sort | uniq -c | sort -nr
58 Server hostname: timeserver-7fcbc89ff9-pxlzz
42 Server hostname: timeserver-7fcbc89ff9-tsbv5
- 파드 복제복 증가 : service endpoint 대상에 자동 추가
[ssoon@localhost dev-app]$ kubectl scale deployment timeserver --replicas 4
deployment.apps/timeserver scaled
[ssoon@localhost dev-app]$ kubectl get service,ep timeserver -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/timeserver NodePort 10.96.47.27 <none> 80:30000/TCP 2m58s pod=timeserver-pod
NAME ENDPOINTS AGE
endpoints/timeserver 10.244.1.4:80,10.244.1.5:80,10.244.2.5:80 + 1 more... 2m58s
- 반복 접속 해두기 : 부하분산 확인
[ssoon@localhost dev-app]$ while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 | grep name ; sleep 1 ; done
Server hostname: timeserver-7fcbc89ff9-nwnb4
Server hostname: timeserver-7fcbc89ff9-tsbv5
Server hostname: timeserver-7fcbc89ff9-tsbv5
Server hostname: timeserver-7fcbc89ff9-pxlzz
Server hostname: timeserver-7fcbc89ff9-lj277
Server hostname: timeserver-7fcbc89ff9-nwnb4
Server hostname: timeserver-7fcbc89ff9-nwnb4
Server hostname: timeserver-7fcbc89ff9-nwnb4
^C
[ssoon@localhost dev-app]$ for i in {1..100}; do curl -s http://127.0.0.1:30000 | grep name; done | sort | uniq -c | sort -nr
27 Server hostname: timeserver-7fcbc89ff9-tsbv5
25 Server hostname: timeserver-7fcbc89ff9-pxlzz
24 Server hostname: timeserver-7fcbc89ff9-nwnb4
24 Server hostname: timeserver-7fcbc89ff9-lj277
🧿 애플리케이션 업데이트
- 샘플 앱 코드 변경 → 새 0.0.2 버전 태그로 컨테이너 이미지 빌드 → 컨테이너 저장소 Push ⇒ k8s deployment 업데이트 배포
- VERSION 코드 변경
0.0.2
- server.py 코드 변경
...
response_string = now.strftime("The time is %-I:%M:%S %p, VERSION 0.0.2\n")
...
- 변경된 파일을 추가하고, VERSION 파일의 내용을 포함한 커밋 메시지로 커밋한 뒤, 이를 원격 main 브랜치에 푸시합니다.
[ssoon@localhost dev-app]$ git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
[main 8975fc3] VERSION 0.0.2 Changed
4 files changed, 7 insertions(+), 4 deletions(-)
오브젝트 나열하는 중: 11, 완료.
오브젝트 개수 세는 중: 100% (11/11), 완료.
Delta compression using up to 4 threads
오브젝트 압축하는 중: 100% (4/4), 완료.
오브젝트 쓰는 중: 100% (6/6), 531 bytes | 531.00 KiB/s, 완료.
Total 6 (delta 2), reused 0 (delta 0), pack-reused 0
To http://192.168.56.105:3000/devops/dev-app.git
d89b73e..8975fc3 main -> main
branch 'main' set up to track 'origin/main'.
- 도커 허브 확인
- 도커 허브 확인
- kubectl set image: timeserver 배포의 이미지를 dev-app:0.0.2로 업데이트하여 새로운 버전의 애플리케이션을 사용하게 합니다.
- kubectl get pods -l pod=timeserver-pod: timeserver-pod 라벨을 가진 파드들이 새로 생성되었고, 상태가 Running인 것을 확인합니다.
- curl http://127.0.0.1:30000: 로컬에서 timeserver 서비스를 호출하여, 응답으로 새로운 버전 0.0.2가 반영된 서버 시간을 확인합니다.
[ssoon@localhost dev-app]$ kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:0.0.2
[ssoon@localhost dev-app]$ kubectl get pods -l pod=timeserver-pod
NAME READY STATUS RESTARTS AGE
timeserver-6c8fcf44ff-cj4jb 1/1 Running 0 47s
timeserver-6c8fcf44ff-dbmn7 1/1 Running 0 53s
timeserver-6c8fcf44ff-j2vxx 1/1 Running 0 53s
timeserver-6c8fcf44ff-vjqk4 1/1 Running 0 47s
[ssoon@localhost dev-app]$ curl http://127.0.0.1:30000
The time is 2:13:03 PM, VERSION 0.0.2
Server hostname: timeserver-6c8fcf44ff-dbmn7
🧿 Gogs Webhooks 설정 : Jenkins Job Trigger
- gogs 에 app.ini 파일 수정 후 컨테이너 재기동
[security]
INSTALL_LOCK = true
SECRET_KEY = ZZfRfSUaLKBpUHd
LOCAL_NETWORK_ALLOWLIST = 192.168.56.105
🧿 Jenkins Item 생성(Pipeline) : item name(SCM-Pipeline)
- GitHub Project는 Jenkins 파이프라인 작업이 연결된 GitHub 저장소를 설정하는 옵션입니다. 이를 통해 Jenkins는 GitHub에서 코드 변경 사항을 추적하고, 해당 저장소의 코드를 자동으로 가져와 빌드 및 배포 작업을 실행할 수 있습니다.
- Gogs Webhook은 Gogs라는 Git 저장소 관리 시스템과 Jenkins를 연결하여, 코드 변경 사항이 발생할 때 자동으로 빌드를 트리거하는 기능입니다. 이 Webhook을 설정하면, Gogs 저장소에 푸시된 코드나 다른 이벤트가 발생할 때 Jenkins가 이를 감지하고 자동으로 파이프라인을 실행합니다.
- Build Trigger는 빌드를 자동으로 시작하는 "트리거" 조건을 설정하는 곳입니다. 예를 들어, 코드가 변경되었을 때나 특정 시간에 빌드가 실행되게 설정할 수 있습니다. 다양한 트리거 조건을 설정하여, 사용자가 직접 빌드를 시작하지 않아도 자동으로 빌드가 실행되게 할 수 있습니다.
- SCM (Source Code Management) 은 Jenkins 파이프라인에서 코드를 관리하고 빌드할 소스 저장소를 설정하는 부분입니다. Jenkins는 SCM을 통해 소스 코드를 자동으로 가져와 빌드를 시작할 수 있습니다.
🧿 Jenkinsfile 작성 후 Git push : 호스트에서 직접 git 작업
- Jenkinsfile
- agent any:
- 이 파이프라인은 어떤 노드에서 실행할지 지정합니다. any는 모든 노드에서 실행할 수 있다는 의미입니다.
- environment:
- DOCKER_IMAGE = 'kschoi728/dev-app'는 Docker 이미지 이름을 환경 변수로 지정합니다. 이후에 Docker 이미지를 빌드하고 푸시할 때 사용됩니다.
- stages:
- 파이프라인은 여러 단계를 stage로 나누어 각 작업을 순차적으로 실행합니다.
각 단계별 설명:
1. Checkout (코드 가져오기)
- git 명령어를 사용하여 Git 저장소에서 코드를 가져옵니다.
- branch: 'main'은 main 브랜치에서 코드를 가져오겠다는 의미입니다.
- url: 'http://192.168.56.105:3000/devops/dev-app.git'는 Git 저장소 URL을 지정한 것입니다.
- credentialsId: 'gogs-crd'는 Gogs의 인증 자격증명을 사용하여 저장소에 접근합니다. 이 자격증명은 Jenkins에 미리 설정되어 있어야 합니다.
2. Read VERSION (VERSION 파일 읽기)
- readFile('VERSION').trim()을 사용하여 VERSION 파일에서 버전 정보를 읽어옵니다.
- env.DOCKER_TAG = version은 읽어온 버전을 환경 변수 DOCKER_TAG에 저장하여, 이후 Docker 이미지 태그로 사용합니다.
3. Docker Build and Push (Docker 이미지 빌드 및 푸시)
- docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd'): Docker Hub 레지스트리로 이미지를 푸시하기 위한 인증 정보를 설정합니다.
- def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}"): DOCKER_IMAGE와 DOCKER_TAG를 조합하여 Docker 이미지를 빌드합니다.
- appImage.push(): 빌드한 이미지를 Docker Hub에 푸시합니다.
- appImage.push("latest"): latest 태그로도 이미지를 푸시합니다. 이는 최신 버전을 나타내는 태그입니다.
4. post: 파이프라인 실행 후 후속 작업
- success: 빌드가 성공적으로 완료되면 "Docker image has been built and pushed successfully!" 메시지를 출력합니다.
- failure: 빌드가 실패하면 "Pipeline failed. Please check the logs." 메시지를 출력합니다.
pipeline {
agent any
environment {
DOCKER_IMAGE = 'kschoi728/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.56.105: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."
}
}
}
- VERSION 파일 : 0.0.3 수정
0.0.3
- server.py 파일 : 0.0.3 수정
...
response_string = now.strftime("The time is %-I:%M:%S %p, VERSION 0.0.3\n")
...
- 작성된 파일 push
[ssoon@localhost dev-app]$ git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
[main 78856b0] VERSION 0.0.3 Changed
3 files changed, 48 insertions(+), 2 deletions(-)
create mode 100644 Jenkinsfile
오브젝트 나열하는 중: 8, 완료.
오브젝트 개수 세는 중: 100% (8/8), 완료.
Delta compression using up to 4 threads
오브젝트 압축하는 중: 100% (4/4), 완료.
오브젝트 쓰는 중: 100% (5/5), 1.07 KiB | 1.07 MiB/s, 완료.
Total 5 (delta 1), reused 0 (delta 0), pack-reused 0
To http://192.168.56.105:3000/devops/dev-app.git
8975fc3..78856b0 main -> main
branch 'main' set up to track 'origin/main'.
- Jenkins 트리거 빌드 확인
- 도커 저장소 확인
- Gogs WebHook 기록 확인
- k8s 에 신규 버전 적용
[ssoon@localhost dev-app]$ curl http://127.0.0.1:30000
The time is 3:03:55 PM, VERSION 0.0.2
Server hostname: timeserver-6c8fcf44ff-dbmn7
[ssoon@localhost dev-app]$ kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:0.0.3
deployment.apps/timeserver image updated
[ssoon@localhost dev-app]$ curl http://127.0.0.1:30000
The time is 3:04:23 PM, VERSION 0.0.3
Server hostname: timeserver-7fb8f9b694-nhznv
'CICD 맛보기' 카테고리의 다른 글
3주차 - Jenkins CI/ArgoCD + K8S : Jenkins CI + Argo CD + K8S(Kind) (0) | 2024.12.16 |
---|---|
3주차 - Jenkins CI/ArgoCD + K8S : Jenkins CI/CD + K8S(Kind) (0) | 2024.12.16 |
3주차 - Jenkins CI/ArgoCD + K8S : 실습환경 (0) | 2024.12.16 |
2주 : GitHub Actions CI/CD : 2 (0) | 2024.12.12 |
2주 : GitHub Actions CI/CD : 1 (0) | 2024.12.11 |
Comments