출처 : AEKS 3기 스터디
# 작업 디렉토리 생성 후 이동
mkdir cicd-labs
cd cicd-labs
# cicd-labs 작업 디렉토리 IDE(VSCODE 등)로 열어두기
#
cat <<EOT > docker-compose.yaml
services:
jenkins:
container_name: jenkins
image: jenkins/jenkins
restart: unless-stopped
networks:
- cicd-network
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:
- cicd-network
ports:
- "10022:22"
- "3000:3000"
volumes:
- gogs-data:/data
volumes:
jenkins_home:
gogs-data:
networks:
cicd-network:
driver: bridge
EOT
# 배포
docker compose up -d
docker compose ps
# 기본 정보 확인
for i in gogs jenkins ; do echo ">> container : $i <<"; docker compose exec $i sh -c "whoami && pwd"; echo; done
# 도커를 이용하여 각 컨테이너로 접속
docker compose exec jenkins bash
*exit*
docker compose exec gogs bash
*exit*


# Jenkins 초기 패스워드 확인
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
# Jenkins 접속
open "http://127.0.0.1:8080" 

#ip 확인
ifconfig | grep 192.168 

# 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 2000 -f docker # macOS(Container)
chgrp docker /var/run/docker.sock
ls -l /var/run/docker.sock
usermod -aG docker jenkins
cat /etc/group | grep docker
exit
--------------------------------------------
# 소켓 파일에 docker 그룹을 다시 지정
docker compose exec --privileged -u root jenkins chgrp docker /var/run/docker.sock
# 확인
docker compose exec jenkins docker info



open "http://127.0.0.1:3000/install" # macOS데이터베이스 유형 : SQLite3
애플리케이션 URL : http://192.168.35.185:3000 (자신의 IP)
기본 브랜치 : main
관리자 계정 설정 클릭 : 이름(계정명 - devops) , 비밀번호 (qwe123), 이메일 입력


Token 생성



개발용 Repository , DevOps용 Repository 생성
Repository Name : dev-app
Visibility : (Check) This repository is Private
.gitignore : Python
Readme : Default → (Check) initialize this repository with selected files and template
⇒ Create Repository 클릭 : Repo 주소 확인

Repository Name : ops-deploy
Visibility : (Check) This repository is Private
.gitignore : Python
Readme : Default → (Check) initialize this repository with selected files and template

# (옵션) GIT 인증 정보 초기화
git credential-cache exit
#
git config --list --show-origin
#
TOKEN=<각자 Gogs Token>
TOKEN=095fc8096d0d8055c5264f996489cfae34d4fe9e
MyIP=<각자 자신의 PC IP> # Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP 입력 할 것!
MyIP=192.168.35.185
git clone <각자 Gogs dev-app repo 주소>
git clone http://devops:$TOKEN@$MyIP:3000/devops/dev-app.git
#
cd dev-app
#
git --no-pager config --local --list
git config --local user.name "devops"
git config --local user.email "a@a.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
# (참고) python 실행 확인
python3 server.py
curl localhost
curl localhost/healthz
CTRL+C 실행 종료
# 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

# 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/healthzcat > Dockerfile <<EOF
FROM python:3.12
ENV PYTHONUNBUFFERED 1
COPY . /app
WORKDIR /app
CMD python3 server.py
EOFecho "0.0.1" > VERSIONtree
git status
git add .
git commit -m "Add dev-app"
git push -u origin main 


Token 생성
계정 → Account setting

Personal access tokens → Generate new token

token description 입력, Access Permissions (Read, Write, Delete) 선택

발급된 Token 복사

# Install Kind
brew install kind
kind --version
# Install kubectl
brew install kubernetes-cli
kubectl version --client=true
## kubectl -> k 단축키 설정
echo "alias kubectl=kubecolor" >> ~/.zshrc
# Install Helm
brew install helm
helm version

# 툴 설치
brew install krew
brew install kube-ps1
brew install kubectx
# kubectl 출력 시 하이라이트 처리
brew install kubecolor
echo "alias kubectl=kubecolor" >> ~/.zshrc
echo "compdef kubecolor=kubectl" >> ~/.zshrc
# krew 플러그인 설치
kubectl krew install neat stren별도의 kubeconfig를 지정후 사용할것
docker ps
kind create clusterkind get clusters
kind get nodes
kubectl cluster-info
# 노드 정보 확인
kubectl get node -o wide
# 파드 정보 확인
kubectl get pod -A
kubectl get componentstatuses
docker ps
docker images
cat ~/.kube/config
kubectl run nginx --image=nginx:alpine
kubectl get pod -owide
kubectl describe node | grep Taints
kind delete cluster
cat ~/.kube/config
docker ps
# Create a cluster with kind
MyIP=<각자 자신의 PC IP>
MyIP=192.168.35.185
# cicd-labs 디렉터리에서 아래 파일 작성
cd ..
cat > kind-3node.yaml <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
apiServerAddress: "$MyIP"
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
EOF
kind create cluster --config kind-3node.yaml --name myk8s --image kindest/node:v1.32.2

kind get nodes --name myk8s
kubens default
docker network ls
docker inspect kind | jq
kubectl cluster-info


# 노드 정보 확인 : CRI 는 containerd 사용
kubectl get node -o wide
# 파드 정보 확인 : CNI 는 kindnet 사용
kubectl get pod -A -o wide

kubectl get namespaces
docker ps
docker images

# kube-ops-view
# helm show values geek-cookbook/kube-ops-view
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://127.0.0.1:30001/#scale=1.5"
open "http://127.0.0.1:30001/#scale=2"







Kind : Username with password
Username : devops
Password : <Gogs 토큰>
ID : gogs-crd

Kind : Username with password
Username : <도커 계정명>
Password : <도커 계정 암호 혹은 토큰>
ID : dockerhub-crd

Kind : Secret file
File : <kubeconfig 파일 업로드>
ID : k8s-crd



pipeline {
agent any
environment {
DOCKER_IMAGE = '*gjrjr4545*/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.35.185: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=gjrjr4545
cat > timeserver.yaml <<EOF |
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
kubectl apply -f timeserver.yamlwatch -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


# 접속을 위한 curl 파드 생성
kubectl run curl-pod --image=curlimages/curl:latest --command -- sh -c "while true; do sleep 3600; done"
kubectl get pod -owide

PODIP1=<timeserver-Y 파드 IP>
PODIP1=10.244.2.4
kubectl exec -it curl-pod -- curl $PODIP1
kubectl exec -it curl-pod -- curl $PODIP1/healthz
# 로그 확인
kubectl logs deploy/timeserver
kubectl logs deploy/timeserver -f
kubectl stern deploy/timeserver
kubectl stern -l pod=timeserver-pod

POD1NAME=<파드 1개 이름>
POD1NAME=timeserver-8c8b5bdff-4hzsk
kubectl get pod -owide
kubectl delete pod $POD1NAME && kubectl get pod -w
# 셀프 힐링 , 파드 IP 변경 -> 고정 진입점(고정 IP/도메인네임) 필요 => Service
kubectl get deploy,rs,pod -owide

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
kubectl get service,ep timeserver -owide
kubectl exec -it curl-pod -- curl timeserver
kubectl exec -it curl-pod -- curl timeserver/healthz
kubectl exec -it curl-pod -- curl $(kubectl get svc timeserver -o jsonpath={.spec.clusterIP})
# Service(NodePort)로 접속 확인 "노드IP:NodePort"
curl http://127.0.0.1:30000
curl http://127.0.0.1:30000/healthz
# 반복 접속 해두기 : 부하분산 확인
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

출처 : AEKS 3기 스터디
response_string = now.strftime("The time is %-I:%M:%S %p, VERSION 0.0.2\n")# VERSION 변경 : 0.0.2
# server.py 변경 : 0.0.2
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
# 파드 복제복 증가
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



kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:0.0.2 && watch -d "kubectl get deploy,ep timeserver; 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 timeserver
kubectl get pods -l pod=timeserver-pod
curl http://127.0.0.1:30000The time is 6:39:29 AM, VERSION 0.0.2
Server hostname: timeserver-6d5fbcbcc5-mgkp7
출처 : AEKS 3기 스터디
[security]
INSTALL_LOCK = true
SECRET_KEY = Yh0YSZprvBwUlDR
LOCAL_NETWORK_ALLOWLIST = http://192.168.35.18 # 각자 자신의 PC IP , *Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP*
Padload URL : http://*<자신의 집 IP>*:8080/gogs-webhook/?job=SCM-Pipeline/
Content Type : applicaion/json
Secret : qwe123


http://*<mac IP>*:3000/*<Gogs 계정명>*/dev-app

http://*<mac IP>*:3000/*<Gogs 계정명>*/dev-app)
pipeline {
agent any
environment {
DOCKER_IMAGE = 'gjrjr4545/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.35.185: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."
}
}
}git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main



kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:0.0.4 && while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; sleep 1 ; done

# Install kubectl, helm
docker exec -u root -it jenkins /bin/bash
--------------------------------------------
#curl -LO "https://dl.k8s.io/release/v1.32.2/bin/linux/amd64/kubectl"
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl" # macOS
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client=true
#
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
exit
--------------------------------------------
docker compose exec jenkins kubectl version --client=true
docker compose exec jenkins helm version


pipeline {
agent any
environment {
KUBECONFIG = credentials('k8s-crd')
}
stages {
stage('List Pods') {
steps {
sh '''
# Fetch and display Pods
kubectl get pods -A --kubeconfig "$KUBECONFIG"
'''
}
}
}
}
#
cd dev-app
#
mkdir deploy
#
cat > deploy/echo-server-blue.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-server-blue
spec:
replicas: 2
selector:
matchLabels:
app: echo-server
version: blue
template:
metadata:
labels:
app: echo-server
version: blue
spec:
containers:
- name: echo-server
image: hashicorp/http-echo
args:
- "-text=Hello from Blue"
ports:
- containerPort: 5678
EOF
cat > deploy/echo-server-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: echo-server-service
spec:
selector:
app: echo-server
version: blue
ports:
- protocol: TCP
port: 80
targetPort: 5678
nodePort: 30000
type: NodePort
EOF
cat > deploy/echo-server-green.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-server-green
spec:
replicas: 2
selector:
matchLabels:
app: echo-server
version: green
template:
metadata:
labels:
app: echo-server
version: green
spec:
containers:
- name: echo-server
image: hashicorp/http-echo
args:
- "-text=Hello from Green"
ports:
- containerPort: 5678
EOF
#
tree
git add . && git commit -m "Add echo server yaml" && git push -u origin mainkubectl delete deploy,svc timeserverwhile true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; echo ; sleep 1 ; kubectl get deploy -owide ; echo ; kubectl get svc,ep echo-server-service -owide ; echo "------------" ; done

pipeline {
agent any
environment {
KUBECONFIG = credentials('k8s-crd')
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.35.185:3000/devops/dev-app.git', // Git에서 코드 체크아웃
credentialsId: 'gogs-crd' // Credentials ID
}
}
stage('container image build') {
steps {
echo "container image build"
}
}
stage('container image upload') {
steps {
echo "container image upload"
}
}
stage('k8s deployment blue version') {
steps {
sh "kubectl apply -f ./deploy/echo-server-blue.yaml --kubeconfig $KUBECONFIG"
sh "kubectl apply -f ./deploy/echo-server-service.yaml --kubeconfig $KUBECONFIG"
}
}
stage('approve green version') {
steps {
input message: 'approve green version', ok: "Yes"
}
}
stage('k8s deployment green version') {
steps {
sh "kubectl apply -f ./deploy/echo-server-green.yaml --kubeconfig $KUBECONFIG"
}
}
stage('approve version switching') {
steps {
script {
returnValue = input message: 'Green switching?', ok: "Yes", parameters: [booleanParam(defaultValue: true, name: 'IS_SWITCHED')]
if (returnValue) {
sh "kubectl patch svc echo-server-service -p '{\"spec\": {\"selector\": {\"version\": \"green\"}}}' --kubeconfig $KUBECONFIG"
}
}
}
}
stage('Blue Rollback') {
steps {
script {
returnValue = input message: 'Blue Rollback?', parameters: [choice(choices: ['done', 'rollback'], name: 'IS_ROLLBACk')]
if (returnValue == "done") {
sh "kubectl delete -f ./deploy/echo-server-blue.yaml --kubeconfig $KUBECONFIG"
}
if (returnValue == "rollback") {
sh "kubectl patch svc echo-server-service -p '{\"spec\": {\"selector\": {\"version\": \"blue\"}}}' --kubeconfig $KUBECONFIG"
}
}
}
}
}
}



blue-green 혼용


while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; echo ; sleep 1 ; kubectl get deploy -owide ; echo ; kubectl get svc,ep echo-server-service -owide ; echo "------------" ; done


kubectl delete deploy echo-server-blue echo-server-green && kubectl delete svc echo-server-service
출처 : https://argo-cd.readthedocs.io/en/stable/
출처 : hhttps://argo-cd.readthedocs.io/en/stable/developer-guide/architecture/components
# 네임스페이스 생성 및 파라미터 파일 작성
cd cicd-labs
kubectl create ns argocd
cat <<EOF > argocd-values.yaml
dex:
enabled: false
server:
service:
type: NodePort
nodePortHttps: 30002
extraArgs:
- --insecure # HTTPS 대신 HTTP 사용
EOF
# 설치
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --version 7.8.13 -f argocd-values.yaml --namespace argocd # 7.7.10
# 확인
kubectl get pod,svc,ep,secret,cm -n argocd
kubectl get crd | grep argo
kubectl get appproject -n argocd -o yaml
# configmap
kubectl get cm -n argocd argocd-cm -o yaml
kubectl get cm -n argocd argocd-rbac-cm -o yaml
# 최초 접속 암호 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
# Argo CD 웹 접속 주소 확인 : 초기 암호 입력 (admin 계정)
open "http://127.0.0.1:30002" # macOS
# Windows OS경우 직접 웹 브라우저에서 http://127.0.0.1:30002 접속




connection method : VIA HTTPS
Type : git
Project : default
Repo URL : http://*<자신의 집 IP>*:3000/devops/ops-deploy
Username : devops
Password : password

cd cicd-labs
mkdir nginx-chart
cd nginx-chart
mkdir templates
cat > templates/configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
index.html: |
{{ .Values.indexHtml | indent 4 }}
EOF
cat > templates/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: nginx
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
ports:
- containerPort: 80
volumeMounts:
- name: index-html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
volumes:
- name: index-html
configMap:
name: {{ .Release.Name }}
EOF
cat > templates/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
spec:
selector:
app: {{ .Release.Name }}
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: NodePort
EOF
cat > values.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>Nginx version 1.26.1</p>
</body>
</html>
image:
repository: nginx
tag: 1.26.1
replicaCount: 1
EOF
cat > Chart.yaml <<EOF
apiVersion: v2
name: nginx-chart
description: A Helm chart for deploying Nginx with custom index.html
type: application
version: 1.0.0
appVersion: "1.26.1"
EOF
# 이전 timeserver/service(nodeport) 삭제
kubectl delete deploy,svc --allhelm template dev-nginx . -f values.yaml
helm install dev-nginx . -f values.yaml
helm list
kubectl get deploy,svc,ep,cm dev-nginx -owide 
curl http://127.0.0.1:30000
curl -s http://127.0.0.1:30000 | grep version
open http://127.0.0.1:30000 

cat > values.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>Nginx version 1.26.2</p>
</body>
</html>
image:
repository: nginx
tag: 1.26.2
replicaCount: 2
EOF
sed -i '' "s|1.26.1|1.26.2|g" Chart.yaml# helm chart 업그레이드 적용
helm template dev-nginx . -f values.yaml # 적용 전 렌더링 확인 Render chart templates locally and display the output.
helm upgrade dev-nginx . -f values.yaml
# 확인
helm list
kubectl get deploy,svc,ep,cm dev-nginx -owide
curl http://127.0.0.1:30000
curl -s http://127.0.0.1:30000 | grep version
open http://127.0.0.1:30000 


helm uninstall dev-nginxcd cicd-labs
TOKEN=095fc8096d0d8055c5264f996489cfae34d4fe9e
MyIP=192.168.35.185
git clone http://devops:$TOKEN@$MyIP:3000/devops/ops-deploy.git
cd ops-deploy#
git --no-pager config --local --list
git config --local user.name "devops"
git config --local user.email "a@a.com"
git config --local init.defaultBranch main
git config --local credential.helper store
git --no-pager config --local --list
cat .git/configgit --no-pager branch
git remote -v 
VERSION=1.26.1
mkdir nginx-chart
mkdir nginx-chart/templates
cat > nginx-chart/VERSION <<EOF
$VERSION
EOF
cat > nginx-chart/templates/configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
index.html: |
{{ .Values.indexHtml | indent 4 }}
EOF
cat > nginx-chart/templates/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: nginx
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
ports:
- containerPort: 80
volumeMounts:
- name: index-html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
volumes:
- name: index-html
configMap:
name: {{ .Release.Name }}
EOF
cat > nginx-chart/templates/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
spec:
selector:
app: {{ .Release.Name }}
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: NodePort
EOF
cat > nginx-chart/values-dev.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>DEV : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 1
EOF
cat > nginx-chart/values-prd.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>PRD : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 2
EOF
cat > nginx-chart/Chart.yaml <<EOF
apiVersion: v2
name: nginx-chart
description: A Helm chart for deploying Nginx with custom index.html
type: application
version: 1.0.0
appVersion: "$VERSION"
EOF
tree 
helm template dev-nginx nginx-chart -f nginx-chart/values-dev.yaml
helm template prd-nginx nginx-chart -f nginx-chart/values-prd.yaml
DEVNGINX=$(helm template dev-nginx nginx-chart -f nginx-chart/values-dev.yaml | sed 's/---//g')
PRDNGINX=$(helm template prd-nginx nginx-chart -f nginx-chart/values-prd.yaml | sed 's/---//g')
diff <(echo "$DEVNGINX") <(echo "$PRDNGINX") 
git add . && git commit -m "Add nginx helm chart" && git push -u origin main


App Name : dev-nginx
Project Name : default
SYNC POLICY : Manual
SYNC OPTIONS : AUTO-CREATE NAMESPACE(Check)
PRUNE PROPAGATION POLICY
- foreground : 부모(소유자, ex. deployment) 자원을 먼저 삭제함
- background : 자식(종속자, ex. pod) 자원을 먼저 삭제함
- orphan : 고아(소유자는 삭제됐지만, 종속자가 삭제되지 않은 경우) 자원을 삭제함

Source 입력
Repo URL : http://192.168.35.185:3000/devops/ops-deploy 입력
Revision : HEAD
PATH : nginx-chart

DESTINATION 입력
Cluster URL : https://kubernetes.default.svc (기본값)
NAMESPACE : dev-nginx

HELM 선택
작성 후 create 클릭



kubectl get applications -n argocd


# 아래 처럼 yaml 로 APP 생성 가능
kubectl get applications -n argocd
kubectl get applications -n argocd -o yaml | kubectl neat
# 배포 확인
kubectl get all -n dev-nginx -o wide


1.26.2 버전으로 업데이트 후 반영 확인
#
VERSION=1.26.2
cat > nginx-chart/VERSION <<EOF
$VERSION
EOF
cat > nginx-chart/values-dev.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>DEV : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 2
EOF
cat > nginx-chart/values-prd.yaml <<EOF
indexHtml: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>PRD : Nginx version $VERSION</p>
</body>
</html>
image:
repository: nginx
tag: $VERSION
replicaCount: 2
EOF
cat > nginx-chart/Chart.yaml <<EOF
apiVersion: v2
name: nginx-chart
description: A Helm chart for deploying Nginx with custom index.html
type: application
version: 1.0.0
appVersion: "$VERSION"
EOF
#
git add . && git commit -m "Update nginx version $(cat nginx-chart/VERSION)" && git push -u origin main





출처 : https://kubernetes.io/blog/2021/05/14/using-finalizers-to-control-deletion/
metadata.finalizers 필드에 정의된 Finalizer(예: resources-finalizer.argocd.argoproj.io)가 있으면, 리소스 삭제 요청 시 즉시 삭제되지 않고 "Terminating" 상태로 전환됩니다.Application 리소스 삭제 요청 시, ArgoCD Finalizer가 삭제를 지연시킵니다.Application 리소스를 완전히 삭제합니다.Application 리소스 삭제를 허용합니다.#
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dev-nginx
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
helm:
valueFiles:
- values-dev.yaml
path: nginx-chart
repoURL: http://192.168.35.185:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
destination:
namespace: dev-nginx
server: https://kubernetes.default.svc
EOF
# Argo CD App 삭제
kubectl delete applications -n argocd dev-nginxcat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: prd-nginx
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
destination:
namespace: prd-nginx
server: https://kubernetes.default.svc
project: default
source:
helm:
valueFiles:
- values-prd.yaml
path: nginx-chart
repoURL: http://192.168.35.185:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
EOF
kubectl delete applications -n argocd prd-nginxRepo(ops-deploy) 에 webhooks → Gogs 선택

API 테스트 진행

cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dev-nginx
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
helm:
valueFiles:
- values-dev.yaml
path: nginx-chart
repoURL: http://192.168.35.185:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
destination:
namespace: dev-nginx
server: https://kubernetes.default.svc
EOF
#
cd cicd-labs/ops-deploy/nginx-chart
# 테스트 1
sed -i -e "s|replicaCount: 2|replicaCount: 3|g" values-dev.yaml
git add values-dev.yaml && git commit -m "Modify nginx-chart : values-dev.yaml" && git push -u origin main
watch -d kubectl get all -n dev-nginx -o wide
# 테스트 2
sed -i -e "s|replicaCount: 3|replicaCount: 5|g" values-dev.yaml
git add values-dev.yaml && git commit -m "Modify nginx-chart : values-dev.yaml" && git push -u origin main
watch -d kubectl get all -n dev-nginx -o wide



kubectl delete applications -n argocd dev-nginx
출처 : AEKS 3기 스터디
cd ops-deploy
#
mkdir dev-app
# 도커 계정 정보
DHUSER=<도커 허브 계정>
DHUSER=gjrjr4545
# 버전 정보
VERSION=0.0.1cat > dev-app/VERSION <<EOF
$VERSION
EOF
cat > dev-app/timeserver.yaml <<EOF
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:$VERSION
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 30
httpGet:
path: /healthz
port: 80
scheme: HTTP
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
imagePullSecrets:
- name: dockerhub-secret
EOF
cat > dev-app/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: timeserver
spec:
selector:
pod: timeserver-pod
ports:
- port: 80
targetPort: 80
protocol: TCP
nodePort: 30000
type: NodePort
EOFgit add . && git commit -m "Add dev-app deployment yaml" && git push -u origin main

#
echo $MyIP
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: timeserver
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
path: dev-app
repoURL: http://$MyIP:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
destination:
namespace: default
server: https://kubernetes.default.svc
EOFkubectl get applications -n argocd timeserver
kubectl get applications -n argocd timeserver -o yaml | kubectl neat
kubectl describe applications -n argocd timeserver
kubectl get deploy,rs,pod
kubectl get svc,ep timeserver 
curl http://127.0.0.1:30000
curl http://127.0.0.1:30000/healthz 

dev-app Repo에 VERSION 업데이트 시 → ops-deploy Repo 에 dev-app 에 파일에 버전 정보 업데이트 작업 추가
Jenkins 파일 추가
pipeline {
agent any
environment {
DOCKER_IMAGE = 'gjrjr4545/dev-app' // Docker 이미지 이름
GOGSCRD = credentials('gogs-crd')
}
stages {
stage('dev-app Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.35.185: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")
}
}
}
}
stage('ops-deploy Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.35.185:3000/devops/ops-deploy.git', // Git에서 코드 체크아웃
credentialsId: 'gogs-crd' // Credentials ID
}
}
stage('ops-deploy version update push') {
steps {
sh '''
OLDVER=$(cat dev-app/VERSION)
NEWVER=$(echo ${DOCKER_TAG})
sed -i -e "s/$OLDVER/$NEWVER/" dev-app/timeserver.yaml
sed -i -e "s/$OLDVER/$NEWVER/" dev-app/VERSION
git add ./dev-app
git config user.name "devops"
git config user.email "a@a.com"
git commit -m "version update ${DOCKER_TAG}"
git push http://${GOGSCRD_USR}:${GOGSCRD_PSW}@*192.168.254.127*:3000/devops/ops-deploy.git
'''
}
}
}
post {
success {
echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
}
failure {
echo "Pipeline failed. Please check the logs."
}
}
}cd dev-app
# VERSION, server.py 0.0.4 -> 0.0.8 으로 변경
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main


# server.py , VERSION 0.0.8 -> 0.0.9
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main\
Argo Image Updater는 Kubernetes 애플리케이션의 컨테이너 이미지를 자동으로 업데이트하는 도구로, Argo CD 생태계의 확장 기능입니다. 주요 목적은 CI 파이프라인에서 새 이미지가 레지스트리에 푸시될 때 GitOps 방식을 유지하면서 배포를 자동화하는 것입니다.
latest 태그, 정규식 패턴, 시맨틱 버전(SemVer) 등을 기반으로 업데이트 대상을 결정합니다.
출처 : https://lakescript.net/entry/ArgoCD-Image-Updater#argocd-imageupdater
출처 : https://lakescript.net/entry/ArgoCD-Image-Updater#argocd-imageupdater
| 기능 | Argo CD | Argo Image Updater |
|---|---|---|
| 주요 역할 | Git 저장소의 선언적 상태를 클러스터에 동기화 | 새 컨테이너 이미지 감지 및 버전 자동 갱신 |
| 자동화 범위 | Git 변경 감지 후 배포 | 이미지 레지스트리 변경 감지 후 Git/배포 조정 |
| 의존성 | 독립적 운영 가능 | Argo CD와 연동되어 사용됨 |
| 업데이트 트리거 | Git 커밋, 웹훅, 수동 동기화 | 주기적 레지스트리 스캔 또는 웹훅 |
| 사용 사례 | 배포 상태 관리, 롤백, 드리프트 감지 | CI 파이프라인과 CD 간 연속적 배포 자동화 |
semver, latest, 정규식 기반 필터링 등 다양한 업데이트 정책을 지원하며, 롤아웃 전 검증(헬스 체크)과 결합할 수 있습니다.ArgoCD Image Updater를 사용함에 있어 다음과 같은 제약사항이 있습니다.
semver/latest 등 전략 혼합 시 오류 가능성.
출처 : https://beer1.tistory.com/68
출처 : https://beer1.tistory.com/68
| 구분 | App-of-Apps 패턴 | 기존 방식 |
|---|---|---|
| 관리 방식 | 모든 애플리케이션 설정을 YAML로 Git에 저장 (GitOps 준수) | 수동 생성/삭제 (Git 추적 불가) |
| 확장성 | 템플릿화된 설정 재사용 가능 (Helm/Kustomize 지원) | 매번 수동 입력 필요 (실수 가능성 ↑) |
| 배포 범위 | 멀티 클러스터/네임스페이스 일괄 관리 가능 | 개별 애플리케이션 단위 관리 |
| 의존성 제어 | Sync Waves로 배포 순서 지정 가능 | 순차적 배포를 위한 별도 스크립트 필요 |
| 감사 추적 | Git 커밋 기록으로 변경 이력 추적 가능 | UI/CLI 작업 기록 누락 |
cd cicd-labs
git clone https://github.com/argoproj/argocd-example-apps.gittree argocd-example-apps/apps 
helm template -f argocd-example-apps/apps/values.yaml argocd-example-apps/appscat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: apps
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
path: apps
repoURL: https://github.com/argoproj/argocd-example-apps.git
targetRevision: HEAD
destination:
namespace: argocd
server: https://kubernetes.default.svc
syncPolicy:
automated:
prune: true
EOFkubectl get applications -n argocd --show-labels 




kubectl delete applications -n argocd appsArgo Rollouts는 Kubernetes 환경에서 고급 배포 전략(Canary, Blue-Green, Progressive Delivery)을 구현하기 위한 오픈소스 도구입니다.
기본 Kubernetes의 Deployment 리소스는 롤링 업데이트만 지원하지만, Argo Rollouts는세분화된 트래픽 제어, 자동화된 롤백, 메트릭 기반 검증등을 제공합니다.
Rollout이라는 커스텀 리소스를 사용해 배포 전략을 YAML로 선언합니다| 기능 | 기본 Deployment | Argo Rollouts |
|---|---|---|
| 배포 전략 | 롤링 업데이트만 지원 | Canary, Blue-Green 등 고급 전략 지원 |
| 트래픽 제어 | X | Istio, NGINX 등과 연동 가능 |
| 자동 롤백 | 수동으로만 가능 | 메트릭 기반 자동 롤백 |
| 배포 상태 분석 | X | Prometheus 등과 연동해 실시간 분석 |
출처 : AEKS 3기 스터디
cd cicd-labs
kubectl create ns argo-rollouts
cat <<EOT > argorollouts-values.yaml
dashboard:
enabled: true
service:
type: NodePort
nodePort: 30003
EOT
# 설치: 2.35.1
helm install argo-rollouts argo/argo-rollouts --version 2.39.2 -f argorollouts-values.yaml --namespace argo-rollouts# 확인
kubectl get all -n argo-rollouts
kubectl get crds 
open "http://127.0.0.1:30003" 
kubernetes 서비스 선 배포 후에 아래와 같은 롤아웃 진행
spec:
replicas: 5
strategy:
canary:
steps:
- setWeight: 20
- pause: {}
- setWeight: 40
- pause: {duration: 10}
- setWeight: 60
- pause: {duration: 10}
- setWeight: 80
- pause: {duration: 10}
kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/rollout.yaml
kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/basic/service.yaml
kubectl get rollout
kubectl describe rollout
kubectl get pod -l app=rollouts-demo
kubectl get svc,ep rollouts-demo
kubectl get rollouts rollouts-demo -o json | grep rollouts-demo





KUBE_EDITOR="vi" kubectl edit rollouts rollouts-demo
kubectl get rollout --watch
watch -d kubectl get pod -l app=rollouts-demo -owide --show-labels






docker compose down --volumes --remove-orphans && kind delete cluster --name myk8s