Ssoon

3주차 - Jenkins CI/ArgoCD + K8S : Jenkins CI + K8S(Kind) 본문

CICD 맛보기

3주차 - Jenkins CI/ArgoCD + K8S : Jenkins CI + K8S(Kind)

구구달스 2024. 12. 16. 21:37

 

CloudNet@ 가시다님이 진행하는 CI/CD 맛보기 스터디

Jenkins CI + K8S(Kind)

🧿 kind 소개

https://kind.sigs.k8s.io/

Kind Kubernetes IN Docker의 약자로, Kubernetes 클러스터를 로컬 환경에서 Docker 컨테이너로 실행할 수 있게 해주는 도구입니다.

Kind의 주요 특징

  1. 로컬 Kubernetes 클러스터:
    • Kind는 로컬 개발 환경에서 Kubernetes 클러스터를 쉽게 만들 수 있도록 도와줍니다.
    • 클러스터는 Docker 컨테이너로 실행되며, 여러 노드를 가질 수 있어 실제 Kubernetes 환경처럼 테스트할 수 있습니다.
  2. 빠르고 간편한 설정:
    • 복잡한 설정 없이 간단한 명령어로 Kubernetes 클러스터를 생성하고, 클러스터에서 애플리케이션을 실행하고 테스트할 수 있습니다.
  3. 다중 노드 클러스터 지원:
    • 컨트롤 플레인(관리 노드)과 워커 노드(작업을 실행하는 노드)를 모두 생성할 수 있습니다.
    • 개발 환경에서 다중 노드 클러스터를 구현할 수 있어, 실제 Kubernetes 환경과 유사하게 테스트할 수 있습니다.
  4. 개발 및 테스트 용도:
    • 로컬 환경에서 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개를 추가. 워커 노드는 애플리케이션을 실행합니다.
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

 2 warnings found (use docker --debug to expand):
 - 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: 이 메시지는 두 가지 주요 원인이 있을 수 있음을 나타냅니다:
    1. 이미지가 존재하지 않음: Docker Hub에 kschoi728/dev-app:0.0.1 이미지가 존재하지 않는 경우.
    2. 인증 문제: 이미지에 접근하기 위해 인증이 필요하지만, 해당 인증이 없거나 잘못된 경우.
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는 빌드를 자동으로 시작하는 "트리거" 조건을 설정하는 곳입니다. 예를 들어, 코드가 변경되었을 때나 특정 시간에 빌드가 실행되게 설정할 수 있습니다. 다양한 트리거 조건을 설정하여, 사용자가 직접 빌드를 시작하지 않아도 자동으로 빌드가 실행되게 할 수 있습니다.
  1.  

  • SCM (Source Code Management) 은 Jenkins 파이프라인에서 코드를 관리하고 빌드할 소스 저장소를 설정하는 부분입니다. Jenkins는 SCM을 통해 소스 코드를 자동으로 가져와 빌드를 시작할 수 있습니다.


🧿 Jenkinsfile 작성 후 Git push : 호스트에서 직접 git 작업

  • Jenkinsfile

  1. agent any:
    • 이 파이프라인은 어떤 노드에서 실행할지 지정합니다. any는 모든 노드에서 실행할 수 있다는 의미입니다.
  2. environment:
    • DOCKER_IMAGE = 'kschoi728/dev-app'는 Docker 이미지 이름을 환경 변수로 지정합니다. 이후에 Docker 이미지를 빌드하고 푸시할 때 사용됩니다.
  3. 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

 

Comments