(⎈|N/A:N/A) root@DESKTOP-8S932ET:~# kind create cluster --name myk8s --image kindest/node:v1.32.8 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
apiServerAddress: "0.0.0.0"
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
- containerPort: 30003
hostPort: 30003
- role: worker
EOF
Creating cluster "myk8s" ...
✓ Ensuring node image (kindest/node:v1.32.8) 🖼
✓ 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
Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:~# ls
yes yes.pub
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:~# ls
yes yes.pub
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:~# cd /usr/local/
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:/usr/local# ls
bin etc games include lib man sbin share src tektoncd-cli-0.42.0_Linux-64bit.deb
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:/usr/local# cd
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:~# ls
yes yes.pub
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:~# mkdir ~/cicd-labs
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:~# ls
cicd-labs yes yes.pub
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:~# cd cicd-labs/
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:~/cicd-labs# kind get nodes --name myk8s
myk8s-control-plane
myk8s-worker
(⎈|kind-myk8s:N/A) root@DESKTOP-8S932ET:~/cicd-labs# kubens default
✔ Active namespace is "default"
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c97fa93d706c kindest/node:v1.32.8 "/usr/local/bin/entr…" 2 minutes ago Up 2 minutes 0.0.0.0:30000-30003->30000-30003/tcp, 0.0.0.0:45769->6443/tcp myk8s-control-plane
052045c35377 kindest/node:v1.32.8 "/usr/local/bin/entr…" 2 minutes ago Up 2 minutes
cat ~/.kube/config
server: https://0.0.0.0:45769
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.21.203.45 netmask 255.255.240.0 broadcast 172.21.207.255
inet6 fe80::215:5dff:fecb:7947 prefixlen 64 scopeid 0x20<link>
ether 00:15:5d:cb:79:47 txqueuelen 1000 (Ethernet)
RX packets 7085 bytes 10081448 (10.0 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1525 bytes 122892 (122.8 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
}(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# curl -k https://172.21.203.45:45769/version
{
"major": "1",
"minor": "32",
"gitVersion": "v1.32.8",
"gitCommit": "2e83bc4bf31e88b7de81d5341939d5ce2460f46f",
"gitTreeState": "clean",
"buildDate": "2025-08-13T14:21:22Z",
"goVersion": "go1.23.11",
"compiler": "gc",
"platform": "linux/amd64"
}(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs#
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
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
# 설치 확인
kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view
# kube-ops-view 접속 URL 확인 (1.5 , 2 배율)
open "http://*<각자 자신의 WSL Ubuntu Eth0 IP>*:30001/#scale=1.5"
open "http://172.21.203.45:30001/#scale=2"

(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# docker network ls
NETWORK ID NAME DRIVER SCOPE
98c6ea9dcb48 bridge bridge local
d1a972e5533d host host local
70c1f4e24e42 kind bridge local
023ed59033a3 none null local
cat <<EOT > docker-compose.yaml
**services**:
**jenkins**:
container_name: jenkins
image: jenkins/jenkins
restart: unless-stopped
networks:
- **kind**
ports:
- "**8080**:8080"
- "50000:50000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- **jenkins_home**:/var/jenkins_home
**gogs**:
container_name: gogs
image: gogs/gogs
restart: unless-stopped
networks:
- **kind**
ports:
- "10022:22"
- "**3000**:3000"
volumes:
- **gogs-data**:/data
**volumes**:
jenkins_home:
gogs-data:
**networks**:
**kind**:
**external: true**
EOT
**docker compose up -d**
docker compose ps
**docker inspect kind**
| 인터페이스 이름 | 역할 | 생성 주체 | 비고 |
|---|---|---|---|
| eth0 | 실제 물리/가상 네트워크 카드 | OS (Ubuntu) | 외부 인터넷 연결용 (진짜 IP: 172.21.203.45) |

# Jenkins 컨테이너 내부에 도커 실행 파일 설치
**docker compose exec --privileged -u root jenkins bash**
-----------------------------------------------------
id
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update && apt install **docker-ce-cli** curl tree jq yq -y
**docker info
docker ps**
which docker
# Jenkins 컨테이너 내부에서 root가 아닌 jenkins 유저도 docker를 실행할 수 있도록 권한을 부여
~~groupadd -g 989 -f docker # **Windows** WSL2(Container) >> **cat /etc/group** 에서 docker 그룹ID를 지정~~
groupadd -g 2000 -f docker
chgrp docker /var/run/docker.sock
ls -l /var/run/docker.sock
usermod -aG docker jenkins
cat /etc/group | grep docker
exit
****--------------------------------------------
# Jenkins 컨테이너 재기동으로 위 설정 내용을 Jenkins app 에도 적용 필요
~~~~docker compose restart jenkins
~~~~
# jenkins user로 docker 명령 실행 확인
docker compose exec jenkins **id**
docker compose exec jenkins **docker info**
docker compose exec jenkins **docker ps**
docker compose exec jenkins cat /etc/group
hp980724
TOKEN = 47c99740cc9e14bf3f490ace539765f389706ad0


docker exec -it gogs bash
----------------------------------------------
*# (옵션) GIT 인증 정보 초기화
git credential-cache exit*
#
TMOUT=0
pwd
ls
cd /data # 호스트 mount 볼륨 공유 경로
#
git config --list --show-origin
#
TOKEN=***<각자 Gogs Token>***
TOKEN=7aa8e88fac50bb72cbed54d212f1262342108261
MyIP=***<각자 자신의 IP> # mac(PC IP), windows(ubuntu eth0)***
MyIP=192.168.254.110
git clone ***<각자 Gogs dev-app repo 주소>***
**git clone *http://*devops:$TOKEN@$MyIP*:3000/hp980724/dev-app.git***
*Cloning into 'dev-app'...
...*
#
cd /data/dev-app
#
git --no-pager config --local --list
git config --local user.name "hp980724"
git config --local user.email "hp980724@mail.com"
git config --local init.defaultBranch **main**
git config --local credential.helper **store**
git --no-pager config --local --list
cat .git/config
#
git --no-pager branch
git remote -v
# server.py 파일 작성
cat > **server.py** <<EOF
*from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from datetime import datetime
import socket
class RequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
match self.path:
case '/':
now = datetime.now()
hostname = socket.gethostname()
response_string = now.strftime("The time is %-I:%M:%S %p, VERSION **0.0.1**\n")
response_string += f"Server hostname: {hostname}\n"
self.respond_with(200, response_string)
case '/healthz':
self.respond_with(200, "Healthy")
case _:
self.respond_with(404, "Not Found")
def respond_with(self, status_code: int, content: str) -> None:
self.send_response(status_code)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(bytes(content, "utf-8"))
def startServer():
try:
server = ThreadingHTTPServer(('', **80**), RequestHandler)
print("Listening on " + ":".join(map(str, server.server_address)))
server.serve_forever()
except KeyboardInterrupt:
server.shutdown()
if __name__== "__main__":
startServer()*
EOF
~~# (참고) python 실행 확인
python3 server.py
curl localhost
curl localhost/healthz~~
# Dockerfile 생성
cat > **Dockerfile** <<EOF
FROM python:3.12
ENV PYTHONUNBUFFERED 1
COPY . /app
WORKDIR /app
CMD python3 server.py
EOF
# VERSION 파일 생성
echo "**0.0.1**" > VERSION
#
tree
git status
git add .
git commit -m "Add dev-app"
**git push -u origin main**
...


dckr_pat_dmkS8UMD8bxc_dYXsRRLAvo29BA

일련의 자동화 작업의 순서들의 집합인 Pipeline을 통해 CI/CD 파이프라인을 구축한다.
| 구성요소 | 설명 |
|---|---|
| Master(Node) | 웹 UI 및 Job 스케줄러 역할 (요즘은 Controller라고 부름) |
| Agent(Node) | 실제 빌드 작업이 수행되는 워커 노드 |
| Job / Pipeline | 자동화 스크립트 (Jenkinsfile) |
| Plugin | Git, Docker, Kubernetes, Slack 등과 연동하는 확장 기능 |
| Credential | Git, Docker Hub 등 외부 서비스 로그인 정보 |
| Workspace | Job이 실행되는 디렉터리 |

pipeline {
agent any
environment {
DOCKER_IMAGE = 'hp980724/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://http://172.21.203.45/: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."
}
}
}


# 디플로이먼트 오브젝트 배포 : 리플리카(파드 2개), 컨테이너 이미지 >> 아래 도커 계정 부분만 변경해서 배포해보자
DHUSER=<도커 허브 계정명>
*DHUSER=gasida*
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*
Every 2.0s: kubectl get deploy,rs,pod -o wide DESKTOP-8S932ET: Mon Oct 27 23:46:59 2025
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/timeserver 0/2 2 0 117s timeserver-container docker.io/hp980724/dev-app:0.0.1 pod=timeserver-pod
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/timeserver-7fc659bdcd 2 2 0 117s timeserver-container docker.io/hp980724/dev-app:0.0.1 pod=timeserver-pod,pod-template-hash=7fc659bdcd
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/timeserver-7fc659bdcd-d8cp5 0/1 ImagePullBackOff 0 117s 10.244.1.3 myk8s-worker <none> <none>
pod/timeserver-7fc659bdcd-vqrdv 0/1 ImagePullBackOff 0 117s 10.244.1.4 myk8s-worker <none> <none>
image pull error (ErrImagePull / ErrImagePullBackOff) 발생
주로 다음과 같은 3가지의 경우에 발생
kubectl get secret -A # 생성 시 타입 지정
DHUSER=<도커 허브 계정>
DHPASS=<도커 허브 암호 혹은 토큰>
echo $DHUSER $DHPASS
*DHUSER=gasida
DHPASS=dckr_pat_JLKruUO5Ee8BGWhqxgRz50_jmT0
echo $DHUSER $DHPASS*
kubectl create **secret docker-registry dockerhub-secret** \
--docker-server=https://index.docker.io/v1/ \
--docker-**username**=$DHUSER \
--docker-**password**=$DHPASS
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
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# kubectl get deploy,pod
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/timeserver 1/2 2 1 5m1s
NAME READY STATUS RESTARTS AGE
pod/timeserver-7b5b869d64-2zr4h 0/1 ContainerCreating 0 0s
pod/timeserver-7b5b869d64-w65ph 1/1 Running 0 70s
pod/timeserver-7fc659bdcd-d8cp5 0/1 ImagePullBackOff 0 5m1s
pod/timeserver-7fc659bdcd-vqrdv 0/1 Terminating 0 5m1s
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
curl http://127.0.0.1:30000
curl http://127.0.0.1:30000
curl http://127.0.0.1:30000/healthz
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# kubectl get service,ep timeserver -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/timeserver NodePort 10.96.77.129 <none> 80:30000/TCP 17s pod=timeserver-pod
NAME ENDPOINTS AGE
endpoints/timeserver 10.244.1.5:80,10.244.1.6:80 17s
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# curl http://127.0.0.1:30000
The time is 2:51:47 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-w65ph
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# curl http://127.0.0.1:30000/healthz
Healthy(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicdwhile true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; sleep 1 ; done ; done
for i in {1..100}; do curl -s http://127.0.0.1:30000 | grep name; done | sort | uniq -c | sort -nr
The time is 2:52:09 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-w65ph
The time is 2:52:20 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 2:52:11 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-w65ph
The time is 2:52:12 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-w65ph
The time is 2:52:13 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-w65ph
^C
52 Server hostname: timeserver-7b5b869d64-2zr4h
48 Server hostname: timeserver-7b5b869d64-w65ph
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# kubectl scale deployment timeserver --replicas 4
deployment.apps/timeserver scaled
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# kubectl get service,ep timeserver -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/timeserver NodePort 10.96.77.129 <none> 80:30000/TCP 68s pod=timeserver-pod
NAME ENDPOINTS AGE
endpoints/timeserver 10.244.1.5:80,10.244.1.6:80,10.244.1.7:80 + 1 more... 68s
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# 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
The time is 2:52:32 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 2:52:33 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 2:52:34 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-7vczh
The time is 2:52:35 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-vgzrz
The time is 2:52:36 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 2:52:37 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-7vczh
The time is 2:52:38 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 2:52:39 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 2:52:40 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-vgzrz
^C
26 Server hostname: timeserver-7b5b869d64-vgzrz
26 Server hostname: timeserver-7b5b869d64-2zr4h
25 Server hostname: timeserver-7b5b869d64-7vczh
23 Server hostname: timeserver-7b5b869d64-w65ph
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs#
docker exec -it gogs bash
cd /data/dev-app
# VERSION 변경 : 0.0.2
# server.py 변경 : 0.0.2
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# 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
The time is 3:08:15 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-vgzrz
The time is 3:08:16 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 3:08:17 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 3:08:27 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-7vczh
The time is 3:08:19 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 3:08:20 PM, VERSION 0.0.1
Server hostname: timeserver-7b5b869d64-2zr4h
The time is 3:08:21 PM, VERSION 0.0.1
27 Server hostname: timeserver-7b5b869d64-7vczh
26 Server hostname: timeserver-7b5b869d64-w65ph
24 Server hostname: timeserver-7b5b869d64-vgzrz
23 Server hostname: timeserver-7b5b869d64-2zr4h
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# 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"
deployment.apps/timeserver image updated
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# kubectl get deploy,rs,pod,svc,ep -owide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/timeserver 3/4 3 3 23m timeserver-container hp980724/dev-app:0.0.2 pod=timeserver-pod
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/timeserver-64b4759458 3 3 1 15s timeserver-container hp980724/dev-app:0.0.2 pod=timeserver-pod,pod-template-hash=64b4759458
replicaset.apps/timeserver-7b5b869d64 2 2 2 20m timeserver-container docker.io/hp980724/dev-app:0.0.1 pod=timeserver-pod,pod-template-hash=7b5b869d64
replicaset.apps/timeserver-7fc659bdcd 0 0 0 23m timeserver-container docker.io/hp980724/dev-app:0.0.1 pod=timeserver-pod,pod-template-hash=7fc659bdcd
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/timeserver-64b4759458-btfhx 0/1 ContainerCreating 0 2s <none> myk8s-worker <none> <none>
pod/timeserver-64b4759458-mjs7d 1/1 Running 0 15s 10.244.1.9 myk8s-worker <none> <none>
pod/timeserver-64b4759458-mkjdp 0/1 ContainerCreating 0 15s <none> myk8s-worker <none> <none>
pod/timeserver-7b5b869d64-2zr4h 1/1 Running 0 18m 10.244.1.6 myk8s-worker <none> <none>
pod/timeserver-7b5b869d64-7vczh 1/1 Terminating 0 16m 10.244.1.7 myk8s-worker <none> <none>
pod/timeserver-7b5b869d64-vgzrz 1/1 Terminating 0 16m 10.244.1.8 myk8s-worker <none> <none>
pod/timeserver-7b5b869d64-w65ph 1/1 Running 0 20m 10.244.1.5 myk8s-worker <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 172m <none>
service/timeserver NodePort 10.96.77.129 <none> 80:30000/TCP 17m pod=timeserver-pod
NAME ENDPOINTS AGE
endpoints/kubernetes 172.18.0.3:6443 172m
endpoints/timeserver 10.244.1.5:80,10.244.1.6:80,10.244.1.9:80 17m
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# kubectl get deploy timeserver
NAME READY UP-TO-DATE AVAILABLE AGE
timeserver 4/4 4 4 24m
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# kubectl get pods -l pod=timeserver-pod
NAME READY STATUS RESTARTS AGE
timeserver-64b4759458-btfhx 1/1 Running 0 17s
timeserver-64b4759458-gwsdk 1/1 Running 0 14s
timeserver-64b4759458-mjs7d 1/1 Running 0 30s
timeserver-64b4759458-mkjdp 1/1 Running 0 30s
timeserver-7b5b869d64-2zr4h 1/1 Terminating 0 19m
timeserver-7b5b869d64-7vczh 1/1 Terminating 0 16m
timeserver-7b5b869d64-vgzrz 1/1 Terminating 0 16m
timeserver-7b5b869d64-w65ph 1/1 Terminating 0 20m
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# ```bash
# 반복 접속 해두기 : 부하분산 확인
**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
#
curl http://127.0.0.1:30000
```^C
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# 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
The time is 3:09:17 PM, VERSION 0.0.2
Server hostname: timeserver-64b4759458-mjs7d
The time is 3:09:18 PM, VERSION 0.0.2
Server hostname: timeserver-64b4759458-mkjdp
The time is 3:09:19 PM, VERSION 0.0.2
Server hostname: timeserver-64b4759458-btfhx
The time is 3:09:20 PM, VERSION 0.0.2
Server hostname: timeserver-64b4759458-mkjdp
The time is 3:09:21 PM, VERSION 0.0.2
docker exec -it gogs bash
/data/gogs/conf/app.ini 수정BRAND_NAME = Gogs
RUN_USER = git
RUN_MODE = prod
[database]
TYPE = sqlite3
HOST = 127.0.0.1:5432
NAME = gogs
SCHEMA = public
USER = gogs
PASSWORD =
SSL_MODE = disable
PATH = data/gogs.db
[repository]
ROOT = /data/git/gogs-repositories
DEFAULT_BRANCH = main
[server]
DOMAIN = localhost
HTTP_PORT = 3000
EXTERNAL_URL = http://172.21.203.45:3000/
DISABLE_SSH = false
SSH_PORT = 22
START_SSH_SERVER = false
OFFLINE_MODE = false
[email]
ENABLED = false
[auth]
REQUIRE_EMAIL_CONFIRMATION = false
DISABLE_REGISTRATION = false
ENABLE_REGISTRATION_CAPTCHA = true
REQUIRE_SIGNIN_VIEW = false
[user]
ENABLE_EMAIL_NOTIFICATION = false
[picture]
DISABLE_GRAVATAR = false
ENABLE_FEDERATED_AVATAR = false
[session]
PROVIDER = file
[log]
MODE = file
LEVEL = Info
ROOT_PATH = /app/gogs/log
[security]
INSTALL_LOCK = true
SECRET_KEY = SeG2qVPPLWBL5zh
LOCAL_NETWORK_ALLOWLIST = 172.21.203.45
**docker compose restart gogs**






docker exec -it gogs bash
9657b615ff9e:/data/dev-app# pwd
/data/dev-app
9657b615ff9e:/data/dev-app# tree
.
├── Dockerfile
├── VERSION
└── server.py
0 directories, 3 files
9657b615ff9e:/data/dev-app#
pipeline {
agent any
environment {
DOCKER_IMAGE = 'hp980724/dev-app'
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://172.21.203.45:3000/hp980724/dev-app.git',
credentialsId: 'gogs-crd' // Credentials ID
}
}
stage('Read VERSION') {
steps {
script {
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."
}
}
}
ERROR: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Head "http://%2Fvar%2Frun%2Fdocker.sock/_ping": dial unix /var/run/docker.sock: connect: permission denied
docker.sock 접근을 위한 수정
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# stat -c '%g' /var/run/docker.sock
989
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# echo DOCKER_GID=989 > .env
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# ls
docker-compose.yaml
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# vi docker-compose.yaml
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# which docker
/usr/bin/docker
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# vi docker-compose.yaml
(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# cat docker-compose.yaml
services:
jenkins:
container_name: jenkins
image: jenkins/jenkins
networks:
- kind
ports:
- "8080:8080"
- "50000:50000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- jenkins_home:/var/jenkins_home
- /usr/bin/docker:/usr/bin/docker:ro
group_add:
- "${DOCKER_GID}"
restart: unless-stopped
gogs:
container_name: gogs
image: gogs/gogs
restart: unless-stopped
networks:
- kind
ports:
- "10022:22"
- "3000:3000"
volumes:
- gogs-data:/data
volumes:
jenkins_home:
gogs-data:
networks:
kind:
external: true
/usr/bin/docker:/usr/bin/docker:rodocker CLI)를 복사하는 것입니다./usr/bin/docker → 호스트에 설치된 Docker 실행파일/usr/bin/docker → Jenkins 컨테이너 안의 경로:ro → 읽기 전용(Read-Only)으로 마운트 (수정 불가, 안전함)sh 'docker build …' 같은 명령을 실행합니다. 하지만 Jenkins 이미지에는 docker 명령어가 기본 설치돼 있지 않습니다. 그래서 호스트의 docker CLI 바이너리를 그대로 빌려 쓰는 것입니다.group_add: - "${DOCKER_GID}"jenkins 사용자에게, 호스트 도커 소켓(/var/run/docker.sock)이 속한 그룹(GID)의 권한을 추가합니다./var/run/docker.sock 파일을 통해 통신하는데, 이 파일은 보통 다음처럼 되어 있습니다:srw-rw---- root docker /var/run/docker.sock
즉, root나 docker 그룹에 속해야 접근 가능합니다. Jenkins는 기본적으로 jenkins 유저로 실행되기 때문에 접근이 막힙니다. 따라서 .env 파일에 DOCKER_GID=989를 적어두고, 이 값을 group_add로 전달하여 Jenkins 컨테이너 안에서도 jenkins 유저가 도커 그룹(989번)에 포함되도록 만든 것입니다.


(⎈|kind-myk8s:default) root@DESKTOP-8S932ET:~/cicd-labs# 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
deployment.apps/timeserver image updated
The time is 12:46:39 PM, VERSION 0.0.3
Server hostname: timeserver-7577795f7b-q4whg
The time is 12:46:40 PM, VERSION 0.0.3
Server hostname: timeserver-7577795f7b-q4whg

| Name of variable | Expression | JSONPath |
|---|---|---|
| ref | $.ref | 0 |
| repository_name | $.repository.name | 0 |
| pusher_name | $.pusher.username | 0 |
| commit_msg | $.commits[0].message | 0 |
pipeline {
agent any
triggers {
GenericTrigger(
genericVariables: [
[key: 'ref', value: '$.ref'],
[key: 'repository_name', value: '$.repository.name'],
[key: 'pusher_name', value: '$.pusher.username'],
[key: 'commit_msg', value: '$.commits[0].message']
],
causeString: 'Triggered on $ref by $pusher_name',
token: 'my-webhook-token', // Gogs Webhook URL의 token과 일치
printContributedVariables: true, // 초기 디버그용(안정화 후 false)
printPostContent: true, // 초기 디버그용(안정화 후 false)
regexpFilterText: '$ref',
regexpFilterExpression: '^refs/heads/(main|develop)$' // main, develop만 처리
)
}
environment {
GIT_URL = 'http://172.21.203.45:3000/hp980724/dev-app.git'
IMAGE = 'hp980724/dev-app'
}
stages {
stage('Checkout') {
steps {
// Git에서 소스 코드 체크아웃
git url: env.GIT_URL, branch: 'main', credentialsId: 'gogs-crd'
}
}
stage('Read VERSION') {
steps {
// VERSION 파일 읽기
script { env.VERSION = readFile('VERSION').trim() }
echo "Version: ${env.VERSION}, Ref: ${env.ref}, Pusher: ${env.pusher_name}"
}
}
stage('Docker Build & Push') {
when {
expression { return env.ref == 'refs/heads/main' || env.ref == 'refs/heads/develop' }
}
steps {
withCredentials([usernamePassword(credentialsId: 'dockerhub-crd', usernameVariable: 'DOCKERHUB_USER', passwordVariable: 'DOCKERHUB_PASS')]) {
// Docker Hub 로그인
sh 'echo "$DOCKERHUB_PASS" | docker login --username "$DOCKERHUB_USER" --password-stdin'
}
// Docker 빌드
sh 'docker build -t ${IMAGE}:${VERSION} .'
// Docker 푸시
sh 'docker push ${IMAGE}:${VERSION}'
sh 'docker tag ${IMAGE}:${VERSION} ${IMAGE}:latest'
sh 'docker push ${IMAGE}:latest'
}
}
}
post {
success { echo "✅ Pushed: ${IMAGE}:${VERSION} (and latest)" }
failure { echo "❌ Pipeline failed. Check logs." }
always { echo "Build# ${env.BUILD_NUMBER}, Repo=${env.repository_name}, Msg=${env.commit_msg}" }
}
}

pipeline {
agent any
environment {
DOCKER_IMAGE = 'hp980724/dev-app'
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://172.21.203.45:3000/hp980724/dev-app.git',
credentialsId: 'gogs-crd' // Gogs credentials ID
}
}
stage('Get Commit SHA') {
steps {
script {
def commitSHA = sh(script: "git rev-parse HEAD", returnStdout: true).trim()
echo "Commit SHA: ${commitSHA}"
env.DOCKER_TAG = commitSHA
}
}
}
stage('Docker Build and Push') {
steps {
script {
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
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."
}
}
}

kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:0.0.2
kubectl set image: timeserver 배포의 timeserver-container 파드에 이미지를 새로 설정 (0.0.2 버전 사용)kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:latest
deployment 수정
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 4
selector:
matchLabels:
pod: timeserver-pod
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: hp980724/dev-app:latest # latest 이미지
imagePullPolicy: Always # 항상 최신 이미지를 가져옴
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 80
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 5
imagePullSecrets:
- name: dockerhub-secret
EOF
kubectl rollout status deployment timeserver
```