일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- CNI
- argocd
- inplace 업그레이드
- Ingress
- grafana
- service mesh
- eks 업그레이드 전략
- k8s인증
- Kind
- eks auto mode
- aws k8s 업그레이드
- eks 모니터링
- Istio
- Jenkins
- docker
- vpc cni
- WSL
- CICD
- bottlerocket
- loadbalancer
- vagrant
- prometheus
- blue-green 업그레이드
- aws 워크샵
- eks autoscaling
- eks인증
- Kubernetes
- K8S
- irsa
- aws eks
- Today
- Total
WellSpring
AEWS 8주차 - K8S CI/CD 본문
목차
- 0. 실습환경 구성
- 1. Jenkins CI + K8S(Kind)
- 2. Jenkins CI/CD+ K8S(Kind)
- 3. Argo CD + k8s(Kind)
- 4. Jenkins CI + Argo CD + K8S(Kind)
- 5. Argo Image Updator
- 6. Argo CD App-of-apps
- 7. Argo Rollout
※ 본 게재 글은 gasida님의 'AEWS' 강의내용과 실습예제 및 Jenkins / gogs 관련 공식사이트와 Blog 등을 참고하여 작성하였습니다.
[ 들어가며 ... ]
☞ CI/CD 배포 자동화 관련 다양한 제공 툴과 환경이 존재한다. 그 중 EKS 멀티 테넌트 환경에서 Argo CD를 통한 중앙 집중적 관리 아키텍쳐 사례를 알아보고, 이에 따른 관리 특성을 확인해 볼 수 있다.
[ 클러스터 관리 ]
* GS 리테일 Amazon EKS의 모든 것 : 무중단 운영부터 배포 자동화 까지 ( 사례 참조 ) : Link
1. 클러스터 관리
- 멀티 테넌트 환경에서 중앙 어카운트에 ArgoCD 배포 환경 구축 및 이와 연결된 공용 Git Repository 구성 후, 서비스 별 어카운트 멀티 테넌트에 중앙에서 필요한 버전의 배포 관리를 할 수 있다.

2. Add-ons 관리
- application set 환경을 사용함으로 ArgoCD 를 통한 중앙 집중 관리가 가능함

3. 팀 R&R 관리
- 개발팀에서 기능 개발 후, 기본 이미지 생성/빌드 하여 ECR 등 Repository 에 올리고
- 플랫폼팀에서 ArgoCD 환경 구축하여 배포 관리를 한다.

4. Configuration 관리
- 중앙 ArgoCD 를 통해 필요한 수백~수천 개의 구성 관련 yaml 파일을 관리함

[ 구축 적용 사례 ]
* 서비스 별 EKS 클러스터 구성 관리 전략 ( 서비스 간 영향도 최소화 )

. 멀티 테넌트 관리의 문제점 발생 및 해결방안 : EKS 구조의 복잡도 상승 -> 영향도 분산관리 방안 모색

[ 아키텍쳐 측면 ]
a. EKS 업그레이드 이슈 : 점점 빨라지는 EKS 버전 배포관리 period ( AWS : 최장 14개월까지 Support 기간 연장 )
b. 로드밸런서 변경 관리 : 필요한 업그레이드 이후, 신규 Ingress 에 서비스 정보 갱신 필요
☞ K8s 영역 외에 별도로 로드밸런서 생성 후, 필요한 Target Group Binding은 POD 단에서 직접 수행

C. 멀티 클러스터 ( A-A 구조 ) : ALB 가중치 기반으로 Traffic 분산 관리

d. 무중단 업그레이드 환경 제공 : Stateless - S3 / Stateful - PV (EBS 환경)

[ R&R 측면 ]
☞ k8s 개발자 들은 개발역량에만 집중 -> 배포는 최대한 쉽게 (click!!)
중앙집중적, 일관성 있는 배포 관리 가능!!



1. GitOps 환경 : 메니페스트 관리 ( 개발자 요구사항 전달 -> 플랫폼팀 : 요구 조건을 Template 변환 엔진을 통해 manifest 자동생성 제공 )
2. Argo 환경 : 배포환경 제공

[ 템플릿 관리 측면 ]
☞ 점점 늘어나는 Manifest 파일을 어떻게 효율적으로 생성, 관리할 것인가??

. 전체 Pipeline의 일목요연한 관리 불가 (Helm)
. 공통영역 overlay 영역에 대해서만 관리 가능 ( Kustomize )
☞ 최적화 된 템플릿 변환 엔진 개발 및 적용 : 동적 정보 => 정적 정보 템플릿화


☞ 배포 요청 시, 필요한 요건을 DB화 된 배포 템플릿을 통해 자동 yaml 생성 후, ArgoCD를 통해 배포 관리함!!

[ 정리 ]

[ 실습 환경 구성도 ]

0. 실습환경 구성
☞ windows (WSL2 + Docker) 경우 아래 1번 과정에 kind(k8s) 설치를 먼저 진행하여 docker network(kind) 생성 후 아래 Jenkins,gogs 생성 할 것
▶ kind 설치 : Windows (WSL2) 사용자

Step1. WSL 2 설치 + Ubuntu 배포판 설치 - Docs , 설명서
Linux용 Windows 하위 시스템 설명서
Linux용 Windows 하위 시스템 설명서의 개요입니다.
learn.microsoft.com
- https://www.lainyzine.com/ko/article/how-to-check-windows-10-edition-version-build-information/
- https://www.lainyzine.com/ko/article/how-to-install-wsl2-and-use-linux-on-windows-10/#windows-10에-wsl2-설치하는-방법
- https://velog.io/@pikamon/Linux-3
a. Powershell 관리자 권한으로 실행
# DISM(배포 이미지 서비스 및 관리) 명령어로 Microsoft-Windows-Subsystem-Linux 기능을 활성화
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
# DISM 명령어로 VirtualMachinePlatform 기능을 활성화
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
# wsl 설치
wsl --install
# 기본값 WSL 2 버전 설정
wsl --set-default-version 2
# wsl 업데이트
wsl --update
b. 윈도우 OS 재부팅
c. Powershell 관리자 권한으로 실행
# 설치 가능한 배포판 확인
wsl --list --online
# Ubuntu 배포판 설치
wsl --install Ubuntu-24.04
...
Enter new UNIX username: <각자 Ubuntu 사용 계정>
New password: <해당 계정 암호>
Retype new password: <해당 계정 암호>
passwd: password updated successfully
Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
---------------------------------------
# 기본 정보 확인
hostnamectl
whoami
id
pwd
# apt 업데이트
sudo apt update
sudo apt install jq htop curl wget ca-certificates net-tools -y
ifconfig eth0
ping -c 1 8.8.8.8
# 빠져나오기
$ exit
---------------------------------------
# 설치된 배포판 확인
wsl -l -v
# Ubuntu 재진입
wsl
Step2. WSL2 에 Docker 설치 - Docs
# WSL2 에 Docker 설치 : 아래 스크립트 실행 후 20초 대기하면 스크립트 실행 됨
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh ./get-docker.sh
...
# 설치 확인
docker info
docker ps
sudo systemctl status docker
cat /etc/group | grep docker
☞ (위 docker 명령어 수행 오류 시) non-root user로 docker 명령어 사용하도록 설정!! - Link
# 1. Create the docker group.
$ sudo groupadd docker
# 2. Add you user to the docker group.
$ sudo usermod -aG docker $USER
# 3. Log out and log back in so that your group membership is re-evaluated.
# or you can run next command
$ newgrp docker
# 4. Verify that you can run docker commands without sudo.
$ docker run helo-world
(옵션) Windows Terminal 설치 및 꾸미기(개발 환경 편리하게)
(옵션) VSCODE 설치 - Download , WSL-VSCODE
WSL 배포에서 프로젝트를 열려면 배포의 명령줄을 열고 다음을 입력합니다. code .
Step3. WSL2 에 kind 및 관리 툴 설치
# 기본 사용자 디렉터리 이동
cd $PWD
pwd
#
sudo systemctl stop apparmor && sudo systemctl disable apparmor
#
sudo apt update && sudo apt-get install bridge-utils net-tools jq tree unzip kubectx kubecolor -y
# Install Kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.27.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
kind --version
# Install kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv ./kubectl /usr/bin
sudo kubectl version --client=true
# Install Helm
curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
helm version
# Source the completion
source <(kubectl completion bash)
echo 'source <(kubectl completion bash)' >> ~/.bashrc
# Alias kubectl to k
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
# Install Kubeps & Setting PS1
git clone https://github.com/jonmosco/kube-ps1.git
echo -e "source $PWD/kube-ps1/kube-ps1.sh" >> ~/.bashrc
cat <<"EOT" >> ~/.bashrc
KUBE_PS1_SYMBOL_ENABLE=true
function get_cluster_short() {
echo "$1" | cut -d . -f1
}
KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
KUBE_PS1_SUFFIX=') '
PS1='$(kube_ps1)'$PS1
EOT
# .bashrc 적용을 위해서 logout 후 터미널 다시 접속 하자
exit
Step4. Kind 기본 사용
# 클러스터 배포 전 확인
docker ps
# Create a cluster with kind
kind create cluster
# 클러스터 배포 확인
kind get clusters
kind get nodes
kubectl cluster-info
# 노드 정보 확인
kubectl get node -o wide
# 파드 정보 확인
kubectl get pod -A
kubectl get componentstatuses
# 컨트롤플레인 (컨테이너) 노드 1대가 실행
docker ps
docker images
# kube config 파일 확인
cat ~/.kube/config
혹은
cat $KUBECONFIG # KUBECONFIG 변수 지정 사용 시
# nginx 파드 배포 및 확인 : 컨트롤플레인 노드인데 파드가 배포 될까요?
kubectl run nginx --image=nginx:alpine
kubectl get pod -owide
# 노드에 Taints 정보 확인
kubectl describe node | grep Taints
Taints: <none>
# 클러스터 삭제
kind delete cluster
# kube config 삭제 확인
cat ~/.kube/config
혹은
cat $KUBECONFIG # KUBECONFIG 변수 지정 사용 시
[ 실행 결과 - 한 눈에 보기 ]


Step5. krew 설치 - https://krew.sigs.k8s.io/docs/user-guide/setup/install/
▶ 컨테이터 2대(Jenkins, gogs) : 호스트 OS 포트 노출(expose)로 접속 및 사용 : Windows (WSL2) 사용자
Step1. Jenkins & gogs container 기동
# 작업 디렉토리 생성 후 이동
mkdir cicd-labs
cd cicd-labs
# cicd-labs 작업 디렉토리 IDE(VSCODE 등)로 열어두기
# kind 설치를 먼저 진행하여 docker network(kind) 생성 후 아래 Jenkins,gogs 생성 할 것
# docker network 확인 : kind 를 사용
docker network ls
...
7e8925d46acb kind bridge 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
# 기본 정보 확인
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
Step2. Jenkins 컨테이너 초기 설정
# Jenkins 초기 암호 확인
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
09a21116f3ce4f27a0ede79372febfb1 ## 자신의 암호 사용!!
# Jenkins 웹 접속 주소 확인 : 계정 / 암호 입력 >> admin / qwe123
웹 브라우저에서 http://127.0.0.1:8080 접속 # Windows
# (참고) 로그 확인 : 플러그인 설치 과정 확인
docker compose logs jenkins -f
Step3. Jenkins URL 설정 : 각자 http://<각자 자신의 WSL2 Ubuntu eth0 IP>:8080/ 를 입력
- Windows WSL2(Ubuntu) 에서 eth0 IP 확인 ifconfig eth0
[ 실행 결과 - 한 눈에 보기 ]






(참고) 실습 완료 후 해당 컨테이너 중지 상태로 둘 경우 → 재부팅 및 이후에 다시 실습을 위해 컨테이너 시작 시
# 실습 완료 후 해당 컨테이너 중지 상태로 둘 경우
docker compose stop
docker compose ps
docker compose ps -a
# mac 재부팅 및 이후에 다시 실습을 위해 컨테이너 시작 시
docker compose start
docker compose ps
(참고) 특정 컨테이너만 삭제 후 다시 초기화 상태로 기동 시
# gogs : 볼륨까지 삭제
docker compose down gogs -v
docker compose up gogs -d
# jenkins : 볼륨까지 삭제
docker compose down jenkins -v
docker compose up jenkins -d
(참고) 모든 실습 후 삭제 시 : docker compose down --volumes --remove-orphans
◈ Jenkins : The open source automation server, support building deploying and automating any project - DockerHub , Github , Docs
CI(지속적 제공)/CD(지속적 배포) 워크플로 예제 : Continuous Integration Server + Continuous Development, Build, Test, Deploy - Link
- 최신 코드 가져오기 : 개발을 위해 중앙 코드 리포지터리에서 로컬 시스템으로 애플리케이션의 최신 코드를 가져옴
- 단위 테스트 구현과 실행 : 코드 작성 전 단위 테스트 케이스를 먼저 작성
- 코드 개발 : 실패한 테스트 케이스를 성공으로 바꾸면서 코드 개발
- 단위 테스트 케이스 재실행 : 단위 테스트 케이스 실행 시 통과(성공!)
- 코드 푸시와 병합 : 개발 소스 코드를 중앙 리포지터리로 푸시하고, 코드 병합
- 코드 병합 후 컴파일 : 변경 함수 코드가 병함되면 전체 애플리케이션이 컴파일된다
- 병합된 코드에서 테스트 실행 : 개별 테스트뿐만 아니라 전체 통합 테스트를 실행하여 문제 없는지 확인
- 아티팩트 배포 : 애플리케이션을 빌드하고, 애플리케이션 서버의 프로덕션 환경에 배포
- 배포 애플리케이션의 E-E 테스트 실행 : 셀레늄 Selenium과 같은 User Interface 자동화 도구를 통해 애플리케이션의 전체 워크플로가 정상 동작하는지 확인하는 종단간 End-to-End 테스트를 실행.
- 소프트웨어 개발 프로세스의 다양한 단계를 자동화하는 도구로서 중앙 소스 코드 리포지터리에서 최신 코드 가져오기, 소스 코드 컴파일, 단위 테스트 실행, 산출물을 다양한 유형으로 패키징, 산출물을 여러 종류의 환경으로 배포하기 등의 기능을 제공.
- 젠킨스는 아파치 톰캣처럼 서블릿 컨테이너 내부에서 실행되는 서버 시스템이다. 자바로 작성됐고, 소프트웨어 개발과 관련된 다양한 도구를 지원.
- 젠킨스는 DSL Domain Specific Language (jenkins file)로 E-E 빌드 수명 주기 단계를 구축한다.
- 젠킨스는 파이프라인이라고 부르는 스크립트를 작성할 수 있는데, 이를 사용해서 각 빌드 단계마다 젠킨스가 수행할 태스트 및 하위 태스크의 순서를 정의.
- 순차적이고 종속적인 단계가 시작부터 끝까지 실행되면 최종적으로 사용자가 실행할 수 있는 빌드가 생성됨.
- 만약 빌드 프로세스를 진행하는 중에 특정 단계에서 실패가 발생하며, 이 단계의 출력 결과를 사용하는 다음 단계는 실행되지 않으며 빌드 프로세스 전체가 실패한다.
- 다양한 Plugins 연동
- Build Plugins : Maven, Ant, Gradle …
- VCS Plugins : Git, SVN …
- Languages Plugins : Java, Python, Node.js …
▶ Jenkins 컨테이너에서 호스트에 도커 데몬 사용 설정 (Docker-out-of-Docker) : Windows (WSL2) 사용자

# 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 1001 -f docker # Windows WSL2(Container) >> cat /etc/group 에서 docker 그룹ID를 지정
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
[ 참고 : jenkins docker 명령어 수행 에러 발생 시 ]

◈ Gogs : Gogs is a painless self-hosted Git service - Github , DockerHub , Docs
▶ Gogs 컨테이너 초기 설정 : Repo(Private) - dev-app , ops-deploy : Windows (WSL2) 사용자
Step1. 초기 웹 접속
# 초기 설정 웹 접속
웹 브라우저에서 http://127.0.0.1:3000/install 접속 # Windows
Step2. 초기 설정
- 데이터베이스 유형 : SQLite3
- 애플리케이션 URL : http://**<각자 자신의 WSL2 Ubuntu eth0 IP>**:3000/
- 기본 브랜치 : main
- 관리자 계정 설정 클릭 : 이름(계정명 - 닉네임 사용 devops), 비밀번호(계정암호 qwe123), 이메일 입력

→ Gogs 설치하기 클릭 ⇒ 관리자 계정으로 로그인 후 접속
Step3. [Token 생성]
로그인 후 → Your Settings → Applications : Generate New Token 클릭 - Token Name(devops) ⇒ Generate Token 클릭 : 메모해두기!

## 자신의 Token 메모해 둘것 !!
1f47b3ae2a901ffcba6e5b2bc7a53725ec2bd720
Step4. Repository 생성 ( 2개 : dev-app / ops-deploy )
- New Repository 1 : 개발팀용
- Repository Name : dev-app
- Visibility : (Check) This repository is Private
- .gitignore : Python
- Readme : Default → (Check) initialize this repository with selected files and template
- New Repository 2 : 데브옵스팀용
- Repository Name : ops-deploy
- Visibility : (Check) This repository is Private
- .gitignore : Python
- Readme : Default → (Check) initialize this repository with selected files and template

▶ Gogs 실습을 위한 저장소 설정 : 호스트에서 직접 git 작업 , Windows 경우 WSL2 혹은 호스트에서 작업 둘 다 가능
# (옵션) GIT 인증 정보 초기화
git credential-cache exit
#
git config --list --show-origin
#
TOKEN=<각자 Gogs Token>
TOKEN=1f47b3ae2a901ffcba6e5b2bc7a53725ec2bd720
MyIP=<각자 자신의 PC IP> # Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP 입력 할 것!
MyIP=172.31.79.21
git clone <각자 Gogs dev-app repo 주소>
git clone http://devops:$TOKEN@$MyIP:3000/devops/dev-app.git
Cloning into 'dev-app'...
...
#
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
# 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
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
...
[ 참고 - WSL2 python3 및 pip, venv 설치 ]
https://docs.microsoft.com/en-us/windows/python/web-frameworks
# 1. apt 업데이트
$ sudo apt update & sudo apt upgrade
# 2. apt python3-pip 설치
$ sudo apt install python3-pip
# 3. 추가적으로 venv(파이썬 가상환경 패키지)를 설치한다.
$ sudo apt install python3-venv
# 4. 그리고 가상환경을 설치할 디렉토리로 이동하여 다음 명령어를 입력해 가상환경을 만든다.
$ python3 -m venv .venv
# 5. 가상환경 활성화 & 비활성화
$ source .venv/bin/activate
또는
$ . .venv/bin/activate
$ deactivate
[ gog-git 연동 ]
1. git clone

2. git config 설정

3. 브랜치 및 remote origin 정보호출 확인

4. server.py 응답 테스트

5. git push ( 현재 생성된 파일 , gogs 로 업로드 )

[ 참고 - App Version - Blog ]

◈ 도커 허브 소개 - Docs
도커 허브(Docker Hub)는 도커 이미지 원격 저장소입니다.
사용자들은 도커 허브에 이미지를 업로드하고, 다른 곳에서 자유롭게 재사용(다운로드)할 수 있습니다.
여러 사용자가 자신이 만든 도커 이미지를 서로 자유롭게 공유할 수 있는 장을 마련해 줍니다.
- 단, 도커 허브는 누구나 이미지를 올릴 수 있기 때문에 공식(Official) 라벨이 없는 이미지는 사용법을 찾을 수 없거나 제대로 동작하지 않을 수 있습니다.
- [정보] 도커 악성 이미지를 통한 취약점 공격 - 기사 모음
- Docker Hub is also where you can go to carry out administrative tasks for organizations. If you have a Docker Team or Business subscription, you can also carry out administrative tasks in the Docker Admin Console.
- Key features
- Repositories: Push and pull container images.
- Builds: Automatically build container images from GitHub and Bitbucket and push them to Docker Hub.
- Webhooks: Trigger actions after a successful push to a repository to integrate Docker Hub with other services.
- Docker Hub CLI tool (currently experimental) and an API that allows you to interact with Docker Hub.
- Browse through the Docker Hub API documentation to explore the supported endpoints.
- Administrative task
- Create and manage teams and organizations
- Create a company
- Enforce sign in
- Set up SSO and SCIM
- Use Group mapping
- Carry out domain audits
- Use Image Access Management to control developers' access to certain types of images
- Turn on Registry Access Management
- Docker Hub Webhooks - Docs
- You can use webhooks to cause an action in another service in response to a push event in the repository. Webhooks are POST requests sent to a URL you define in Docker Hub.
- Set up Automated Builds : 저장소를 구성하여 소스 공급자에게 새 코드를 푸시할 때마다 자동으로 이미지를 빌드 - Docs
- Automated builds require a Docker Pro, Team, or Business subscription.
[ 참고 ] Docker Hub quickstart - Docs
- Step 1 : Sign up for a free Docker account
- A Docker ID grants you access to Docker Hub repositories and lets you explore available images from the community and verified publishers. You also need a Docker ID to share images on Docker Hub.
- Explore Docker's core subscriptions to see what else Docker can offer you. → Personal(무료, 6시간 당 200개 Image pulls)
- Step 2 : Create your first repository
- On the Repositories page, select Create repository.
- Name it <your-username>/dev-test-app
- Set the visibility to Private.
- Select Create.
- Step 3 : Build and push a container image to Docker Hub from your computer
- Start by creating a Dockerfile to specify your application as shown below:
# syntax=docker/dockerfile:1 FROM busybox CMD echo "Hello world! This is my first Docker image."
- Run docker build -t <your_username>/my-private-repo . to build your Docker image.
- Run docker run <your_username>/my-private-repo to test your Docker image locally.
- Run docker push <your_username>/my-private-repo to push your Docker image to Docker Hub. You should see output similar to:
# Dockerfile 파일 작성
cat > Dockerfile <<EOF
FROM busybox
CMD echo "Hello world! This is my first Docker image."
EOF
cat Dockerfile
# 빌드
DOCKERID=<자신의 도커 계정명>
DOCKERREPO=<자신의 도커 저장소>
DOCKERID=gasida
DOCKERREPO=dev-test-app
docker build -t $DOCKERID/$DOCKERREPO .
docker images | grep $DOCKERID/$DOCKERREPO
# 실행 확인
docker run $DOCKERID/$DOCKERREPO
# 로그인 확인
docker login
# 푸시
docker push $DOCKERID/$DOCKERREPO

- (참고) Name your local images using one of these methods:
- When you build them, using docker build -t <hub-user>/<repo-name>[:<tag>
- By re-tagging the existing local image with docker tag <existing-image> <hub-user>/<repo-name>[:<tag>].
- By using docker commit <existing-container> <hub-user>/<repo-name>[:<tag>] to commit changes.
▶ [ 실습 ] 자신의 도커 허브 계정에 Token 발급
Step1. 자신의 도커 허브 계정에 dev-app (Private) repo 생성


Step2. 계정 → Account settings

Step3. Security → Personal access tokens

Step4. Generate new token : 만료일(편한대로), Access permissions(Read, Write, Delete)

Step5. 발급된 token 메모
## 자신의 Docker hub Token 메모해 두자!!
[ 도전과제 ]
도전과제1 : Jenkins 를 AWS EKS에 In-Cluster 에 설치(파드로 기동)해서 사용해보자 - KrBlog
도전과제2 : Gogs 를 AWS EKS에 In-Cluster 에 설치(파드로 기동)해서 사용해보자
도전과제3 : Gogs 대신 GitHub Private Repo 를 생성해서 사용해보자
1. Jenkins CI + K8S(Kind)
▶ kind 로 k8s 배포 : Windows (WSL2) 사용자
- 기본 정보 확인
# 클러스터 배포 전 확인
docker ps
mkdir cicd-labs
cd ~/cicd-labs
# WSL2 Ubuntu eth0 IP를 지정
ifconfig eth0
MyIP=<각자 자신의 WSL2 Ubuntu eth0 IP>
MyIP=172.31.79.21
# cicd-labs 디렉터리에서 아래 파일 작성
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
# kind 는 별도 도커 네트워크 생성 후 사용 : 기본값 172.18.0.0/16
docker network ls
docker inspect kind | jq
# k8s api 주소 확인 : 어떻게 로컬에서 접속이 되는 걸까?
kubectl cluster-info
# 노드 정보 확인 : CRI 는 containerd 사용
kubectl get node -o wide
# 파드 정보 확인 : CNI 는 kindnet 사용
kubectl get pod -A -o wide
# 네임스페이스 확인 >> 도커 컨테이너에서 배운 네임스페이스와 다릅니다!
kubectl get namespaces
# 컨트롤플레인/워커 노드(컨테이너) 확인 : 도커 컨테이너 이름은 myk8s-control-plane , myk8s-worker/worker-2 임을 확인
docker ps
docker images
# 디버그용 내용 출력에 ~/.kube/config 권한 인증 로드
kubectl get pod -v6
# kube config 파일 확인 : "server: https://172.19.21.65:35413" 부분에 접속 주소 잘 확인해두자!
cat ~/.kube/config
ls -l ~/.kube/config

▶ kube-ops-view 설치
# 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 배율) : 아래 접속 주소를 자신의 웹 브라우저에서 접속할 것!
echo "http://127.0.0.1:30001/#scale=1.5"
echo "http://127.0.0.1:30001/#scale=2"
◈ 작업 소개 (프로젝트, Job, Item) : 3가지 유형의 지시 사항 포함
- 작업을 수행하는 시점 Trigger
- 작업 수행 태스크 task가 언제 시작될지를 지시
- 작업을 구성하는 단계별 태스크 Built step
- 특정 목표를 수행하기 위한 태스크를 단계별 step로 구성할 수 있다.
- 이것을 젠킨스에서는 빌드 스텝 build step이라고 부른다.
- 태스크가 완료 후 수행할 명령 Post-build action
- 예를 들어 작업의 결과(성공 or 실패)를 사용자에게 알려주는 후속 동작이나, 자바 코드를 컴파일한 후 생성된 클래스 파일을 특정 위치로 복사 등
- (참고) 젠킨스의 빌드 : 젠킨스 작업의 특정 실행 버전
- 사용자는 젠킨스 작업을 여러번 실행할 수 있는데, 실행될 때마다 고유 빌드 번호가 부여된다.
- 작업 실행 중에 생성된 아티팩트, 콘솔 로드 등 특정 실행 버전과 관련된 모든 세부 정보가 해당 빌드 번호로 저장된다.
▶ Jenkins 설정 : Plugin 설치, 자격증명 설정
Step1.
- Jenkins Plugin 설치

Step2.
- 자격증명 설정 : Jenkins 관리 → Credentials → Globals → Add Credentials
- Gogs Repo 자격증명 설정 : gogs-crd
- Kind : Username with password
- Username : devops
- Password : <Gogs 토큰>
- ID : gogs-crd
- 도커 허브 자격증명 설정 : dockerhub-crd
- Kind : Username with password
- Username : <도커 계정명>
- Password : <도커 계정 암호 혹은 토큰>
- ID : dockerhub-crd
- k8s(kind) 자격증명 설정 : k8s-crd
- Kind : Secret file
- File : <kubeconfig 파일 업로드>
- ID : k8s-crd
- Gogs Repo 자격증명 설정 : gogs-crd
⇒ macOS 사용자 경우, cp ~/.kube/config ./kube-config 복사 후 해당 파일 업로드 하자
⇒ Windows 사용자 경우, kubeconfig 파일은 메모장으로 직접 작성 후 업로드 하자


▶ Jenkins Item 생성(Pipeline) : item name(pipeline-ci)
☞ Pipeline script : < 자신의 도커 허브 계정 >, <자신의 집 IP> 2개 항목 수정 !!
- Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP 입력 할 것!
pipeline {
agent any
environment {
DOCKER_IMAGE = '<자신의 도커 허브 계정>/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://<자신의 집 IP>:3000/devops/dev-app.git', // Git에서 코드 체크아웃
credentialsId: 'gogs-crd' // Credentials ID
}
}
stage('Read VERSION') {
steps {
script {
// VERSION 파일 읽기
def version = readFile('VERSION').trim()
echo "Version found: ${version}"
// 환경 변수 설정
env.DOCKER_TAG = version
}
}
}
stage('Docker Build and Push') {
steps {
script {
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
// DOCKER_TAG 사용
def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
appImage.push()
appImage.push("latest")
}
}
}
}
}
post {
success {
echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
}
failure {
echo "Pipeline failed. Please check the logs."
}
}
}
[ 수정 본 ]
pipeline {
agent any
environment {
DOCKER_IMAGE = 'daniel0324/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://172.31.79.21: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."
}
}
}
[ 실행 결과 - 한 눈에 보기 ]

▶ k8s Deploying an application with Jenkins(pipeline-ci)
Deploying to Kubernetes
- 원하는 상태 설정 시 k8s 는 충족을 위해 노력함 : Kubernetes uses declarative configuration, where you declare the state you want (like “I want 3 copies of my container running in the cluster”) in a configuration file. Then, submit that config to the cluster, and Kubernetes will strive to meet the requirements you specified.
# 디플로이먼트 오브젝트 배포 : 리플리카(파드 2개), 컨테이너 이미지 >> 아래 도커 계정 부분만 변경해서 배포해보자
DHUSER=<도커 허브 계정명>
DHUSER=daniel0324
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 2
selector:
matchLabels:
pod: timeserver-pod
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: docker.io/$DHUSER/dev-app:0.0.1
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 30
httpGet:
path: /healthz
port: 80
scheme: HTTP
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
EOF
watch -d kubectl get deploy,rs,pod -o wide
# 배포 상태 확인 : kube-ops-view 웹 확인
kubectl get events -w --sort-by '.lastTimestamp'
kubectl get deploy,pod -o wide
kubectl describe pod
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 53s default-scheduler Successfully assigned default/timeserver-7cf7db8f6c-mtvn7 to myk8s-worker
Normal BackOff 19s (x2 over 50s) kubelet Back-off pulling image "docker.io/gasida/dev-app:latest"
Warning Failed 19s (x2 over 50s) kubelet Error: ImagePullBackOff
Normal Pulling 4s (x3 over 53s) kubelet Pulling image "docker.io/gasida/dev-app:latest"
Warning Failed 3s (x3 over 51s) kubelet Failed to pull image "docker.io/gasida/dev-app:latest": failed to pull and unpack image "docker.io/gasida/dev-app:latest": failed to resolve reference "docker.io/gasida/dev-app:latest": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
Warning Failed 3s (x3 over 51s) kubelet Error: ErrImagePull
TROUBLESHOOTING : image pull error (ErrImagePull / ErrImagePullBackOff)
- 보통 컨테이너 이미지 정보를 잘못 기입하는 경우에 발생
- This error indicates that Kubernetes was unable to download the container image.
- 혹은 이미지 저장소에 이미지가 없거나, 이미지 가져오는 자격 증명이 없는 경우에 발생
- This typically means that the image name was misspelled in your configuration, the image doesn’t exist in the image repository, or your cluster doesn’t have the required credentials to access the repository.
- Check the spelling of your image and verify that the image is in your repository.
# k8s secret : 도커 자격증명 설정
kubectl get secret -A # 생성 시 타입 지정
DHUSER=<도커 허브 계정>
DHPASS=<도커 허브 암호 혹은 토큰>
echo $DHUSER $DHPASS
DHUSER=gasida
DHPASS=dckr_pat_KWx-0N27iEd1lk8aNvRz8pDrQlI
echo $DHUSER $DHPASS
kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username=$DHUSER \
--docker-password=$DHPASS
# 확인
kubectl get secret
kubectl describe secret
kubectl get secrets -o yaml | kubectl neat # base64 인코딩 확인
SECRET=eyJhdXRocyI6eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsidXNlcm5hbWUiOiJnYXNpZGEiLCJwYXNzd29yZCI6ImRja3JfcGF0X0tXeC0wTjI3aUVkMWxrOGFOdlJ6OHBEclFsSSIsImF1dGgiOiJaMkZ6YVdSaE9tUmphM0pmY0dGMFgwdFhlQzB3VGpJM2FVVmtNV3hyT0dGT2RsSjZPSEJFY2xGc1NRPT0ifX19
echo "$SECRET" | base64 -d ; echo
# 디플로이먼트 오브젝트 업데이트 : 시크릿 적용 >> 아래 도커 계정 부분만 변경해서 배포해보자
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 2
selector:
matchLabels:
pod: timeserver-pod
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: docker.io/$DHUSER/dev-app:0.0.1
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 30
httpGet:
path: /healthz
port: 80
scheme: HTTP
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
imagePullSecrets:
- name: dockerhub-secret
EOF
watch -d kubectl get deploy,rs,pod -o wide
# 확인
kubectl get events -w --sort-by '.lastTimestamp'
kubectl get deploy,pod
# 접속을 위한 curl 파드 생성
kubectl run curl-pod --image=curlimages/curl:latest --command -- sh -c "while true; do sleep 3600; done"
kubectl get pod -owide
# timeserver 파드 IP 1개 확인 후 접속 확인
PODIP1=<timeserver-Y 파드 IP>
PODIP1=10.244.1.3
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
파드 1개 삭제 후 동작 확인 → 접속 확인
#
POD1NAME=<파드 1개 이름>
POD1NAME=timeserver-7954b8f6df-l25wx
kubectl get pod -owide
kubectl delete pod $POD1NAME && kubectl get pod -w
# 셀프 힐링 , 파드 IP 변경 -> 고정 진입점(고정 IP/도메인네임) 필요 => Service
kubectl get deploy,rs,pod -owide
Publishing your Service
- Each Pod is given its own cluster-local (internal) IP address, which can be used for communication between Pods within the cluster.
- It’s possible to expose Pods directly on the internet as well as on the node’s IP (with the field hostPort), but unless you’re writing a real-time game server, that’s rarely what you’ll do.
- Typically, and especially when Deployment is used, you will aggregate your Pods into a Service, which provides a single access point with an internal (and optionally external) IP, and load balance requests across your pods.
- 디플로이먼트에 파드 1개가 있다하더라도 서비스를 통해 안정적인 주소를 제공
- Even if you had a Deployment of a single Pod, you’ll still want to create a Service to provide a stable address.
- In addition to load balancing, Services keep track of which Pods are running and capable of receiving traffic. For example, while you may have specified three replicas in your Deployment, that doesn’t mean that three replicas will be available at all times.
- 노드 업그레이드 등 이벤트 발생 시 정상 동작 파드로만 라우팅 처리 : There might only be two if a node is being upgraded, or there could be more than three while you’re rolling out a new version of your Deployment. The Service will only route traffic to running Pods (in the next chapter, we’ll cover some key information you need to provide to make that works smoothly).
- Services are used internally within the cluster to enable communication between multiple applications (a so-called microservice architecture) and offer convenient features, such as service discovery, for this purpose.
- Each Pod and Service in Kubernetes has its own internal cluster IP, so you don’t need to worry about port conflicts between Pods.
- Notice also that this Service has a section named selector, like our Deployment had.
- The Service doesn’t reference the Deployment and actually has no knowledge of the Deployment.
- Instead, it references the set of Pods that have the given label (which, in this case, will be the Pods created by our Deployment).

- Unlike in the Deployment object, the selector section has no matchLabels subsection.
- They are, however, equivalent. Deployment is just using a newer, more expressive syntax in Kubernetes.
- The selectors in the Deployment and in the Service are achieving the same result: specifying the set of Pods that the object is referencing.
# 서비스 생성
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
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/timeserver NodePort 10.96.236.37 <none> 80:30000/TCP 25s pod=timeserver-pod
NAME ENDPOINTS AGE
endpoints/timeserver 10.244.1.2:80,10.244.2.2:80,10.244.3.2:80 25s
# Service(ClusterIP)로 접속 확인 : 도메인네임, ClusterIP
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
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
# 반복 접속 해두기 : 부하분산 확인
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
▶ Update application 테스트

Step1. 샘플 앱 server.py 코드 변경 → 젠킨스(지금 빌드 실행) : 새 0.0.2 버전 태그로 컨테이너 이미지 빌드 → 컨테이너 저장소 Push ⇒ k8s deployment 업데이트 배포
# VERSION 변경 : 0.0.2
# server.py 변경 : 0.0.2
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
Step2. 태그는 버전 정보 사용을 권장 : You can make this tag anything you like, but it’s a good convention to use version numbers.
# 파드 복제복 증가
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.Y && watch -d "kubectl get deploy,ep timeserver; echo; kubectl get rs,pod"
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 $DEPLOYMENT_NAME
kubectl get deploy timeserver
kubectl get pods -l pod=timeserver-pod
#
curl http://127.0.0.1:30000
- The READY column shows how many Pods are serving traffic and how many we requested.
- In this case, all three are ready. The UP-TO-DATE column, however, indicates that only one of these Pods is the current version.
- This is because, rather than replacing all the Pods at once, causing some downtime to the application, by default, Pods are updated with a so-called rolling update strategy—that is, one or several at a time.
▶ Gogs Webhooks 설정 : Jenkins Job Trigger

▷ gogs 에 /data/gogs/conf/app.ini 파일 수정 후 컨테이너 재기동 - issue
[security]
INSTALL_LOCK = true
SECRET_KEY = j2xaUPQcbAEwpIu
LOCAL_NETWORK_ALLOWLIST = 192.168.254.127 # 각자 자신의 PC IP , Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP

- docker compose restart gogs
Step1. 웹 훅 설정
- gogs 에 Webhooks 설정 : Jenkins job Trigger - Setting → Webhooks → Gogs 클릭
- Webhooks are much like basic HTTP POST event triggers. Whenever something occurs in Gogs,
- we will handle the notification to the target host you specify. %!(EXTRA string=https://gogs.io/docs/features/webhook.html)

- Payload URL : http://***<자신의 집 IP>***:8080/gogs-webhook/?job=**SCM-Pipeline**/ , http://172.31.79.21:8080/gogs-webhook/?job=SCM-Pipeline/
- Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP
- Content Type : application/json
- Secret : qwe123
- When should this webhook be triggered? : Just the push event
- Active : Check
⇒ Add webhook
Step2. Trouble shooting ( 오류 발생 시 - 참조 )
(TS) gogs 에 Webhooks 동작이 잘 되지 않을 경우 /data/gogs/conf/app.ini 파일 내에 아래 항목 확인 해 볼것 !!
[server]
DOMAIN = localhost
HTTP_PORT = 3000
EXTERNAL_URL = http://172.19.21.65: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 = FRs5icUGEgh4bVW
LOCAL_NETWORK_ALLOWLIST = 172.19.21.65 ## 요 부분!!
▶ Jenkins Item 생성(Pipeline) : item name(SCM-Pipeline)

- GitHub project : http://***<mac IP>***:3000/***<Gogs 계정명>***/dev-app ← .git 은 제거
- *GitHub project : http://172.31.79.21:3000/devops/dev-app
- Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP
- *GitHub project : http://172.31.79.21:3000/devops/dev-app
- Use Gogs secret : qwe123
- Build Triggers : Build when a change is pushed to Gogs 체크
- Pipeline script from SCM
- SCM : Git
- Repo URL(http://***<mac IP>***:3000/***<Gogs 계정명>***/dev-app)
- Credentials(devops/***)
- Branch(*/main)
- Script Path : Jenkinsfile
- SCM : Git
[ 설정 순서 ]



▶ Jenkinsfile 작성 후 Git push

Step1. Git 작업
# Jenkinsfile 빈 파일 작성
touch Jenkinsfile
# VERSION 파일 : 0.0.3 수정
# server.py 파일 : 0.0.3 수정
Step2. IDE(VSCODE..) 로 Jenkinsfile 파일 작성
- < 자신의 도커 허브 계정 > , <자신의 집 IP> 수정
- Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP
pipeline {
agent any
environment {
DOCKER_IMAGE = '<자신의 도커 허브 계정>/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://<자신의 집 IP>:3000/devops/dev-app.git', // Git에서 코드 체크아웃
credentialsId: 'gogs-crd' // Credentials ID
}
}
stage('Read VERSION') {
steps {
script {
// VERSION 파일 읽기
def version = readFile('VERSION').trim()
echo "Version found: ${version}"
// 환경 변수 설정
env.DOCKER_TAG = version
}
}
}
stage('Docker Build and Push') {
steps {
script {
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
// DOCKER_TAG 사용
def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
appImage.push()
appImage.push("latest")
}
}
}
}
}
post {
success {
echo "Docker image ${DOCKER_IMAGE}:${DOCKER_TAG} has been built and pushed successfully!"
}
failure {
echo "Pipeline failed. Please check the logs."
}
}
}
[ 수정 반영 본 ]
pipeline {
agent any
environment {
DOCKER_IMAGE = 'daniel0324/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://172.31.79.21: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."
}
}
}
Step3. 작성 된 파일 Push
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
Step4. 진행 상태 확인
- Gogs WebHook 기록 확인
- 도커 저장소 확인
- Jenkins 트리거 빌드 확인
Step5. k8s 에 신규 버전 적용
# 신규 버전 적용
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
# 확인
watch -d "kubectl get deploy,ep timeserver; echo; kubectl get rs,pod"
[ 실행 결과 - 한 눈에 보기 ]


[ 도전 과제 ]
도전과제4 : kind 대신 AWS EKS를 사용해서 실습 진행해보자
도전과제5 : Docker Hub 저장소 대신 AWS ECR 저장소를 사용해서 실습 진행해보자
도전과제6 : 디플로이먼트에 파드 컨테이너 이미지에 latest 사용해서 배포 후 이미지 버전 업데이트 후 적용 방안? (예. imagePullPolicy:Always) - Docs
도전과제7 : Multi-Arch(CPU) 컨테이너 이미지를 빌드할 수 있게 Jenkins Pipeline 을 작성(업데이트) 해보자
도전과제8 : Jenkins 에 Gogs Webhook 대신 범용 Webhook 플러그인를 사용해보자
2. Jenkins CI/CD+ K8S(Kind)
▶ Jenkins 컨테이너 내부에 툴 설치 : kubectl(v1.32), helm
# Install kubectl, helm
docker compose exec --privileged -u root jenkins 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
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" # WindowOS
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
▶ Jenkins Item 생성(Pipeline) : item name(k8s-cmd)
pipeline {
agent any
environment {
KUBECONFIG = credentials('k8s-crd')
}
stages {
stage('List Pods') {
steps {
sh '''
# Fetch and display Pods
kubectl get pods -A --kubeconfig "$KUBECONFIG"
'''
}
}
}
}

▶ [K8S CD 실습] Jenkins 를 이용한 blue-green 배포 준비
☞ 디플로이먼트 / 서비스 yaml 파일 작성 - http-echo 및 코드 push
#
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 main
(참고) 직접 블루-그린 업데이트 실행
#
cd deploy
kubectl delete deploy,svc --all
kubectl apply -f .
#
kubectl get deploy,svc,ep -owide
curl -s http://127.0.0.1:30000
#
kubectl patch svc echo-server-service -p '{"spec": {"selector": {"version": "green"}}}'
kubectl get deploy,svc,ep -owide
curl -s http://127.0.0.1:30000
#
kubectl patch svc echo-server-service -p '{"spec": {"selector": {"version": "blue"}}}'
kubectl get deploy,svc,ep -owide
curl -s http://127.0.0.1:30000
# 삭제
kubectl delete -f .
cd ..
▶ Jenkins Item 생성(Pipeline) : item name(k8s-bluegreen) - Jenkins 통한 k8s 기본 배포
Step1. 이전 실습에 디플로이먼트, 서비스 삭제
kubectl delete deploy,svc timeserver
Step2. 모니터링 준비 ( 반복 접속 미리 실행)
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
혹은
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; date ; echo "------------" ; sleep 1 ; done
Step3. pipeline script : Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP
pipeline {
agent any
environment {
KUBECONFIG = credentials('k8s-crd')
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://<자신의 집 IP>: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"
}
}
}
}
}
}
[ 수정 코드 ]
pipeline {
agent any
environment {
KUBECONFIG = credentials('k8s-crd')
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://172.31.79.21: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"
}
}
}
}
}
}
Step4. Jenkins 에서 "지금 배포" 후, 진행 상태 확인!!
Step5. 실습 완료 후 자원 삭제
kubectl delete deploy echo-server-blue echo-server-green && kubectl delete svc echo-server-service
[ 실행 결과 - 한 눈에 보기 ]





3. Argo CD + k8s(Kind)
◈ Argo CD 소개 : Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes
- Application definitions, configurations, and environments should be declarative and version controlled.
- Application deployment and lifecycle management should be automated, auditable, and easy to understand.
- How it works : Argo CD follows the GitOps pattern of using Git repositories as the source of truth for defining the desired application state. Kubernetes manifests can be specified in several ways:
- **kustomize applications****
- **helm charts*****
- jsonnet files
- Plain directory of YAML/json manifests
- Any custom config management tool configured as a config management plugin

[ Flow 요약 ]
개발팀 개발소스 git 에 저장(dev-app) → Jenkins 통해 Devops팀 Repo(ops-deploy)에 저장 → ArgoCD 통해 배포 반영

- API Server : Web UI 대시보드, k8s api 처럼 API 서버 역할
- The API server is a gRPC/REST server which exposes the API consumed by the Web UI, CLI, and CI/CD systems. It has the following responsibilities:
- application management and status reporting
- invoking of application operations (e.g. sync, rollback, user-defined actions)
- repository and cluster credential management (stored as K8s secrets)
- authentication and auth delegation to external identity providers
- RBAC enforcement
- listener/forwarder for Git webhook events
- Repository Server : Git 연결 및 배포할 yaml 생성
- The repository server is an internal service which maintains a local cache of the Git repository holding the application manifests. It is responsible for generating and returning the Kubernetes manifests when provided the following inputs:
- repository URL
- revision (commit, tag, branch)
- application path
- template specific settings: parameters, helm values.yaml
- Application Controller : k8s 리소스 모니터링, Git과 비교
- The application controller is a Kubernetes controller which continuously monitors running applications and compares the current, live state against the desired target state (as specified in the repo). It detects OutOfSync application state and optionally takes corrective action. It is responsible for invoking any user-defined hooks for lifecycle events (PreSync, Sync, PostSync)
- Redis : k8s api와 git 요청을 줄이기 위한 캐싱
- Notification : 이벤트 알림, 트리거
- Dex : 외부 인증 관리
- ApplicationSet Controller : 멀티 클러스터를 위한 App 패키징 관리

- 기능
- Automated deployment of applications to specified target environments
- Support for multiple config management/templating tools (Kustomize, Helm, Jsonnet, plain-YAML)
- Ability to manage and deploy to multiple clusters
- SSO Integration (OIDC, OAuth2, LDAP, SAML 2.0, GitHub, GitLab, Microsoft, LinkedIn)
- Multi-tenancy and RBAC policies for authorization
- Rollback/Roll-anywhere to any application configuration committed in Git repository
- Health status analysis of application resources
- Automated configuration drift detection and visualization
- Automated or manual syncing of applications to its desired state
- Web UI which provides real-time view of application activity
- CLI for automation and CI integration
- Webhook integration (GitHub, BitBucket, GitLab)
- Access tokens for automation
- PreSync, Sync, PostSync hooks to support complex application rollouts (e.g.blue/green & canary upgrades)
- Audit trails for application events and API calls
- Prometheus metrics
- Parameter overrides for overriding helm parameters in Git
- Core Concepts - Docs
- Application A group of Kubernetes resources as defined by a manifest. This is a Custom Resource Definition (CRD).
- Application source type Which Tool is used to build the application.
- Target state The desired state of an application, as represented by files in a Git repository.
- Live state The live state of that application. What pods etc are deployed.
- Sync status Whether or not the live state matches the target state. Is the deployed application the same as Git says it should be?
- Sync The process of making an application move to its target state. E.g. by applying changes to a Kubernetes cluster.
- Sync operation status Whether or not a sync succeeded.
- Refresh Compare the latest code in Git with the live state. Figure out what is different.
- Health The health of the application, is it running correctly? Can it serve requests?
- Tool A tool to create manifests from a directory of files. E.g. Kustomize. See Application Source Type.
- Configuration management tool See Tool.
- Configuration management plugin A custom tool.
3-1. Argo CD 설치 및 기본 설정 - helm_chart
Step1. Argo CD 설치
# 네임스페이스 생성 및 파라미터 파일 작성
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
applications.argoproj.io 2024-04-14T08:12:16Z
applicationsets.argoproj.io 2024-04-14T08:12:17Z
appprojects.argoproj.io 2024-04-14T08:12:16Z
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
...
data:
policy.csv: ""
policy.default: ""
policy.matchMode: glob
scopes: '[groups]'
# 최초 접속 암호 확인
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
XxJMMJUv8MHZa-kk
# Argo CD 웹 접속 주소 확인 : 초기 암호 입력 (admin 계정)
open "http://127.0.0.1:30002" # macOS
# Windows OS경우 직접 웹 브라우저에서 http://127.0.0.1:30002 접속
Step2. Argo CD 웹 접속 확인
User info → UPDATE PASSWORD 로 admin 계정 암호 변경 (qwe12345)
Step3. 기본 정보 확인 (Settings) : Clusters, Projects, Accounts

Step4. ops-deploy Repo 등록 : Settings → Repositories → CONNECT REPO 클릭
- connection method : VIA HTTPS
- Type : git
- Project : default
- Repo URL : http://***<자신의 집 IP>***:3000/devops/ops-deploy
http://172.31.79.21:3000/devops/ops-deploy
- Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP
- Username : devops
- Password : <Gogs 토큰>
⇒ 입력 후 CONNECT 클릭

[ 실행 결과 - 한 눈에 보기 ]
1. ArgoCD 설치 후, 구성내용 확인

2. ArgoCD Repository 생성 및 접속 테스트

3-2. (기초) helm chart 를 통한 배포 실습
#
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 --all
# 직접 배포 해보기
helm 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
# value 값 변경 후 적용 해보기 : version/tag, replicaCount
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-nginx
[ 실행 결과 - 한 눈에 보기 ]



3-3. Repo(ops-deploy) 에 nginx helm chart 를 Argo CD를 통한 배포 1
Step1. git 작업 ( ※ TOKEN , MyIP 변수 지정 필수 !! )
#
cd cicd-labs
TOKEN=<>
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/config
#
git --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 nginx-chart
nginx-chart
├── Chart.yaml
├── VERSION
├── templates
│ ├── configmap.yaml
│ ├── deployment.yaml
│ └── service.yaml
├── values-dev.yaml
└── values-prd.yaml
#
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

Step2. Argo CD에 App 등록 : Application → NEW APP
- GENERAL
- App Name : dev-nginx
- Project Name : default
- SYNC POLICY : Manual
- AUTO-CREATE NAMESPACE : 클러스터에 네임스페이스가 없을 시 argocd에 입력한 이름으로 자동 생성
- APPLY OUT OF SYNC ONLY : 현재 동기화 상태가 아닌 리소스만 배포
- SYNC OPTIONS : AUTO-CREATE NAMESPACE(Check)
- PRUNE PROPAGATION POLICY
- foreground : 부모(소유자, ex. deployment) 자원을 먼저 삭제함
- background : 자식(종속자, ex. pod) 자원을 먼저 삭제함
- orphan : 고아(소유자는 삭제됐지만, 종속자가 삭제되지 않은 경우) 자원을 삭제함
- Source
- Repo URL : 설정되어 있는 것 선택
- Revision : HEAD
- PATH : nginx-chart
- DESTINATION
- Cluster URL : <기본값>
- NAMESPACE : dev-nginx
- HELM
- Values files : values-dev.yaml
⇒ 작성 후 상단 CREATE 클릭
- PRUNE : GIt에서 자원 삭제 후 배포시 K8S에서는 삭제되지 않으나, 해당 옵션을 선택하면 삭제시킴
- FORCE : --force 옵션으로 리소스 삭제
- APPLY ONLY : ArgoCD의 Pre/Post Hook은 사용 안함 (리소스만 배포)
- DRY RUN : 테스트 배포 (배포에 에러가 있는지 한번 확인해 볼때 사용)

- dev-nginx App 클릭 후 상세 정보 확인 → DIFF 클릭 확인

#
kubectl get applications -n argocd
NAME SYNC STATUS HEALTH STATUS
dev-nginx OutOfSync Missing
kubectl describe applications -n argocd dev-nginx
# 상태 모니터링
kubectl get applications -n argocd -w
# 반복 접속 시도
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; date ; echo "------------" ; sleep 1 ; done
Step3. SYNC 클릭 으로 K8S(Live) 반영 확인 : 생성될 리소스 확인

# 아래 처럼 yaml 로 APP 생성 가능
kubectl get applications -n argocd
kubectl get applications -n argocd -o yaml | kubectl neat
# 배포 확인
kubectl get all -n dev-nginx -o wide
[ 실행 결과 - 한 눈에 보기 ]




▶ (옵션) GitOps 방식(?)을 무시하고 K8S(Live)를 수정 시도해보기!
- 동작 확인을 위해서 반복 접속
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; date ; echo "------------" ; sleep 1 ; done
- configmap (dev-nginx) 클릭

- LIVE MANIFEST 에 EDIT 클릭 후 index.html 내용 추가 → SAVE

- K8S(Live) 상태 확인
#
kubectl get cm -n dev-nginx dev-nginx -o yaml
apiVersion: v1
data:
index.html: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Nginx!</title>
</head>
<body>
<h1>Hello, Kubernetes!</h1>
<p>DEV testtest : Nginx version 1.26.1</p>
</body>
</html>
...
labels:
argocd.argoproj.io/instance: dev-nginx
myname: tester
...
# (추가) kubectl 로 직접 k8s 추가 시 >> 이후 ArgoCD LIVE 에서 확인!
kubectl get cm -n dev-nginx dev-nginx --show-labels
kubectl label cm dev-nginx -n dev-nginx study=aews
kubectl get cm -n dev-nginx dev-nginx --show-labels
# 변경된 CM 적용을 위해서 롤아웃
kubectl rollout restart deployment -n dev-nginx dev-nginx
#
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; date ; echo "------------" ; sleep 1 ; done
- ArgoCD 에서 Git(원본) 과 LIVE간 차이점 DIFF 발견

- 하지만 Git에 없던 정보는 DIFF 차이점 발견이 되지 않는다!
☞ GitOps를 위해서는, 반드시 단일 진실 공급원(Single Source Of Truth, SSOT)를 통해서 관리를 할 것!
Step4. 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

Step5. Argo CD 웹 확인 → REFRESH 클릭 - Interval


☞ How often does Argo CD check for changes to my Git or Helm repository ?
The default polling interval is 3 minutes (180 seconds). You can change the setting by updating the timeout.reconciliation value in the argocd-cm config map
kubectl get cm -n argocd argocd-cm -o yaml | grep timeout
timeout.hard.reconciliation: 0s
timeout.reconciliation: 180s
Step6. SYNC 클릭 → SYNCHRONIZE 클릭
# 배포 확인
kubectl get all -n dev-nginx -o wide
Step7. Argo CD 웹에서 App 삭제

watch -d kubectl get all -n dev-nginx -o wide

[ 도전과제 ] 위 ArgoCD App 을 신규 생성할 때 Helm 에 prd-value 를 사용할 수 있게 설정 후 배포 실습 해보자!




3-4. Repo(ops-deploy) 에 nginx helm chart 를 Argo CD를 통한 배포 2 : ArgoCD Declarative Setup
- ArgoCD Declarative Setup - Project, applications(ArgoCD App 자체를 yaml로 생성), ArgoCD Settings - Docs
- (참고) K8S Finalizers 와 Argo Finalizers 동작 - Docs , Blog

- Kubernetes에서 finalizers는 리소스의 metadata.finalizers 필드에 정의된 이름 목록으로, 리소스가 삭제 요청을 받았을 때(즉, kubectl delete나 API 호출로 삭제가 시작될 때) 바로 제거되지 않고, 지정된 작업이 완료될 때까지 "종료 중"(Terminating) 상태로 유지되게 합니다.
- ArgoCD는 이 메커니즘을 활용해 애플리케이션 삭제 시 관리 대상 리소스의 정리(cleanup)를 제어합니다.
- ArgoCD에서 가장 흔히 사용되는 finalizer는 resources-finalizer.argocd.argoproj.io입니다. 이 finalizer는 애플리케이션이 삭제될 때 해당 애플리케이션이 관리하는 모든 리소스(예: Pod, Service, ConfigMap 등)를 함께 삭제하도록 보장합니다.
- ArgoCD Finalizers의 목적
- 리소스 정리 보장: 애플리케이션 삭제 시 관련 리소스가 남지 않도록 보장합니다. 이는 GitOps 워크플로우에서 선언적 상태를 유지하는 데 중요합니다.
- 의도치 않은 삭제 방지: finalizer가 없으면 실수로 Argo App을 삭제해도 K8S 리소스가 남아 혼란이 생길 수 있습니다. finalizer는 이를 방지합니다.
- App of Apps 패턴 지원: 여러 애플리케이션을 계층적으로 관리할 때, 상위 애플리케이션 삭제 시 하위 리소스까지 정리되도록 합니다.
- dev-nginx App 생성 및 Auto SYNC ( finalizers : - resources-finalizer.argocd.argoproj.io )
#
echo $MyIP
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://$MyIP:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
destination:
namespace: dev-nginx
server: https://kubernetes.default.svc
EOF
#
kubectl get applications -n argocd dev-nginx
kubectl get applications -n argocd dev-nginx -o yaml | kubectl neat
kubectl describe applications -n argocd dev-nginx
kubectl get pod,svc,ep,cm -n dev-nginx
#
curl http://127.0.0.1:30000
open http://127.0.0.1:30000
# Argo CD App 삭제
kubectl delete applications -n argocd dev-nginx

- prd-nginx App 생성 및 Auto SYNC
#
cat <<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://$MyIP:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
EOF
#
kubectl get applications -n argocd prd-nginx
kubectl describe applications -n argocd prd-nginx
kubectl get pod,svc,ep,cm -n prd-nginx
#
curl http://127.0.0.1:30000
open http://127.0.0.1:30000
# Argo CD App 삭제
kubectl delete applications -n argocd prd-nginx

3-5. Repo(ops-deploy) 에 Webhook 를 통해 Argo CD 에 즉시 반영 trigger하여 k8s 배포 할 수 있게 설정 - Docs
- Repo(ops-deploy) 에 webhooks 설정 : Gogs 선택
- Payload URL : http://172.31.79.21:30002/api/webhook
- Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP
- 나머지 항목 ‘기본값’ ⇒ Add webhook
- 이후 생성된 webhook 클릭 후 Test Delivery 클릭 후 정상 응답 확인
- Payload URL : http://172.31.79.21:30002/api/webhook
- dev-nginx App 생성 및 Auto SYNC
#
echo $MyIP
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://$MyIP:3000/devops/ops-deploy
targetRevision: HEAD
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
destination:
namespace: dev-nginx
server: https://kubernetes.default.svc
EOF
#
kubectl get applications -n argocd dev-nginx
kubectl get applications -n argocd dev-nginx -o yaml | kubectl neat
kubectl describe applications -n argocd dev-nginx
kubectl get pod,svc,ep,cm -n dev-nginx
#
curl http://127.0.0.1:30000
open http://127.0.0.1:30000
- Git(Gogs) 수정 후 ArgoCD 즉시 반영 확인
#
cd cicd-labs/ops-deploy/nginx-chart
#
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
#
sed -i -e "s|replicaCount: 3|replicaCount: 4|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
#
sed -i -e "s|replicaCount: 4|replicaCount: 2|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
- Argo CD App 삭제
kubectl delete applications -n argocd dev-nginx
[ 실행 결과 - 한 눈에 보기 ]




[ 도전과제 ]
▶ ArgoCD 에 User 에 API Key 발급 후 ArgoCD API(SYNC/DELETE)를 호출하여 Trigger 동작 설정 - Docs
- ArgoCD Account (admin 계정) Token 생성
#
kubectl describe cm -n argocd argocd-cm
kubectl get cm -n argocd argocd-cm -o yaml
# admin 계정에 apiKey 추가
kubectl edit cm -n argocd argocd-cm
-----------------------------------
accounts.admin: apiKey, login
-----------------------------------
# 확인
argocd account list
argocd account get --account admin
# 토큰 생성 : argocd cli 설치 필요
argocd account generate-token --account admin
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJhZG1pbjphcGlLZXkiLCJuYmYiOjE3Mjg0ODc2MTcsImlhdCI6MTcyODQ4NzYxNywianRpIjoiYWUzZmYxMWItY2NlMC00MGJlLTgyNTMtNjQ5NjQ3YjM2MTkxIn0.7fXXeKxjwRvFCsbbSZuGmUERpYEKDRtTUXzTESIAA-w
#
argocd account get --account admin
Name: admin
Enabled: true
Capabilities: login, apiKey, login
Tokens:
ID ISSUED AT EXPIRING AT
ae3ff11b-cce0-40be-8253-649647b36191 2024-10-10T00:26:57+09:00 never
- Argo CD’s API : http://127.0.0.1:30002/swagger-ui : 접속 후 sync 검색 → POST 클릭 후 sample 확인

- curl(GET) 로 applications 정보 확인
ARGOCD_SERVER=http://127.0.0.1:30002
ARGOCDTOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJhZG1pbjphcGlLZXkiLCJuYmYiOjE3Mjg0ODc2MTcsImlhdCI6MTcyODQ4NzYxNywianRpIjoiYWUzZmYxMWItY2NlMC00MGJlLTgyNTMtNjQ5NjQ3YjM2MTkxIn0.7fXXeKxjwRvFCsbbSZuGmUERpYEKDRtTUXzTESIAA-w"
curl -s $ARGOCD_SERVER/api/v1/applications -H "Authorization: Bearer $ARGOCDTOKEN" | jq
...
- postman(POST) 으로 sync 실행 → Send 후 확인
- POST : http://127.0.0.1:30002/api/v1/applications/sample-app-1/sync
# [Headers] Key : Value
Authorization : Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJhZG1pbjphcGlLZXkiLCJuYmYiOjE3Mjg0ODc2MTcsImlhdCI6MTcyODQ4NzYxNywianRpIjoiYWUzZmYxMWItY2NlMC00MGJlLTgyNTMtNjQ5NjQ3YjM2MTkxIn0.7fXXeKxjwRvFCsbbSZuGmUERpYEKDRtTUXzTESIAA-w
# [Body] JSON 선택
{
"prune": true
}
- 삭제해보자 DELETE : http://127.0.0.1:30002/api/v1/applications/sample-app-1
# [Body] JSON 선택
{}
▶ ArgoCD에 HPA를 포함하는 Sample Deployment 를 배포해보고 App 생성 시 추가 설정을 고려해보자 - Docs , KrBlog
예시)
spec:
destination:
server: https://kubernetes.default.svc
ignoreDifferences:
- group: apps
jsonPointers:
- /spec/replicas
- /metadata/annotations/deployment.kubernetes.io/revision
kind: Deployment
- group: autoscaling
jsonPointers:
- /status
kind: HorizontalPodAutoscaler
project: default
source:
path: apps/ui
repoURL: https://sampleapp.amazonaws.com/v1/repos/eks-gitops-repo
targetRevision: main
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- RespectIgnoreDifferences=true
4. Jenkins CI + Argo CD + K8S(Kind)
[ Full CI/CD 구성도 ]

▶ Repo(ops-deploy) 기본 코드 작업
#
cd ops-deploy
#
mkdir dev-app
# 도커 계정 정보
DHUSER=<도커 허브 계정>
DHUSER=gasida
# 버전 정보
VERSION=0.0.1
#
cat > 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
EOF
#
git add . && git commit -m "Add dev-app deployment yaml" && git push -u origin main
▶ Repo(ops-deploy) 를 바라보는 ArgoCD App 생성
#
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
EOF
#
kubectl 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
open http://127.0.0.1:30000

▶ Repo(dev-app) 코드 작업
- dev-app Repo에 VERSION 업데이트 시 → ops-deploy Repo 에 dev-app 에 파일에 버전 정보 업데이트 작업 추가
- 기존 버전 정보는 VERSION 파일 내에 정보를 가져와서 변수 지정 : OLDVER=$(cat dev-app/VERSION)
- 신규 버전 정보는 environment 도커 태그 정보를 가져와서 변수 지정 : NEWVER=$(echo ${DOCKER_TAG})
- 이후 sed 로 ops-deploy Repo 에 dev-app/VERSION, timeserver.yaml 2개 파일에 ‘기존 버전’ → ‘신규 버전’으로 값 변경
- 이후 ops-deploy Repo 에 git push ⇒ Argo CD App Trigger 후 AutoSync 로 신규 버전 업데이트 진행

- 아래는 dev-app 에 위치한 Jenkinsfile 로 젠킨스에 SCM-Pipeline(SCM:git) 으로 사용되고 있는 파일을 수정해서 실습에 사용
- Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP
pipeline {
agent any
environment {
DOCKER_IMAGE = '<자신의 도커 허브 계정>/dev-app' // Docker 이미지 이름
GOGSCRD = credentials('gogs-crd')
}
stages {
stage('dev-app Checkout') {
steps {
git branch: 'main',
url: 'http://<자신의 집 IP>:3000/devops/dev-app.git', // Git에서 코드 체크아웃
credentialsId: 'gogs-crd' // Credentials ID
}
}
stage('Read VERSION') {
steps {
script {
// VERSION 파일 읽기
def version = readFile('VERSION').trim()
echo "Version found: ${version}"
// 환경 변수 설정
env.DOCKER_TAG = version
}
}
}
stage('Docker Build and Push') {
steps {
script {
docker.withRegistry('https://index.docker.io/v1/', 'dockerhub-crd') {
// DOCKER_TAG 사용
def appImage = docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
appImage.push()
appImage.push("latest")
}
}
}
}
stage('ops-deploy Checkout') {
steps {
git branch: 'main',
url: 'http://<자신의 집 IP>: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 '' "s/$OLDVER/$NEWVER/" dev-app/timeserver.yaml
sed -i '' "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}@<자신의 집 IP>: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."
}
}
}
[ 수정 코드 ]
pipeline {
agent any
environment {
DOCKER_IMAGE = 'daniel0324/dev-app' // Docker 이미지 이름
GOGSCRD = credentials('gogs-crd')
}
stages {
stage('dev-app Checkout') {
steps {
git branch: 'main',
url: 'http://172.31.79.21: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://172.31.79.21: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."
}
}
}
- 아래는 dev-app (Repo) 에서 git push 수행
# [터미널] 동작 확인 모니터링
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; echo ; kubectl get deploy timeserver -owide; echo "------------" ; sleep 1 ; done
#
cd cicd-labs/dev-app
# VERSION 파일 수정 : 0.0.3
# server.py 파일 수정 : 0.0.3
# git push : VERSION, server.py, Jenkinsfile
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main

[ 실행결과 - 한 눈에 보기 ]





▶ Full CI/CD 동작 확인 : Argo CD app Trigger 후 AutoSync 로 신규 버전 업데이트 진행 확인

- dev-app Repo 에서 한번 더 버전 업데이트 수행
# [터미널] 동작 확인 모니터링
while true; do curl -s --connect-timeout 1 http://127.0.0.1:30000 ; echo ; kubectl get deploy timeserver -owide; echo "------------" ; sleep 1 ; done
# VERSION 파일 수정 : 0.0.4
# server.py 파일 수정 : 0.0.4
# git push : VERSION, server.py, Jenkinsfile
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
# VERSION 파일 수정 : 0.0.5
# server.py 파일 수정 : 0.0.5
# git push : VERSION, server.py, Jenkinsfile
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main
☞ 개발팀 dev-app Repo 에서만 코드 업데이트 작업 시, jenkins pipeline 에서 ops-deploy Repo 에 버전 정보 업데이트를 하고, 이후 Argo CD가 자동으로 신규 버전 정보로 배포를 하게 된다.
5. Argo Image Updator
ArgoCD 기존 방식

ArgoCD Image Updater


▶ KRBlog
6. Argo CD App-of-apps
[ 개념 이해 ]
▶ App-of-apps (배포를 배포) 동작 - Docs & [악분일상] Blog* , Youtube / Blog1 , Blog2
[ 기존 방식 ]

[ App of apps : Root Application, Child Application ]




▶ [ArgoCD Docs] Cluster Boostrapping : app of apps pattern - Docs , Github
- App 생성

#
cd cicd-labs
git clone https://github.com/argoproj/argocd-example-apps.git
#
tree argocd-example-apps/apps
argocd-example-apps/apps
├── Chart.yaml
├── templates
│ ├── helm-guestbook.yaml
│ ├── helm-hooks.yaml
│ ├── kustomize-guestbook.yaml
│ ├── namespaces.yaml
│ └── sync-waves.yaml
└── values.yaml
#
helm template -f argocd-example-apps/apps/values.yaml argocd-example-apps/apps
# you need to create and sync your parent app
cat <<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
EOF
#
kubectl get applications -n argocd --show-labels
NAME SYNC STATUS HEALTH STATUS LABELS
apps Synced Healthy <none>
helm-guestbook OutOfSync Missing argocd.argoproj.io/instance=apps
helm-hooks OutOfSync Missing argocd.argoproj.io/instance=apps
kustomize-guestbook OutOfSync Missing argocd.argoproj.io/instance=apps
sync-waves OutOfSync Missing argocd.argoproj.io/instance=apps
# 상태 모니터링
kubectl get applications -n argocd -w


- You can either sync via the UI, firstly filter by the correct label:

- Then select the "out of sync" apps and sync:


- 확인
# 확인
kubectl get pod -n helm-guestbook -l app=helm-guestbook
NAME READY STATUS RESTARTS AGE
helm-guestbook-57c97698c4-hsnxn 0/1 Running 1 (2s ago) 64s
# Readiness Probe 실패 : CPU Arch 가 AMD64 로 mac M에서 동작 불가
kubectl describe pod -n helm-guestbook -l app=helm-guestbook
Containers:
helm-guestbook:
Container ID: containerd://2e53557351150a119825820c3891915e1dd811ed4311e78caebebcac47aabd37
Image: gcr.io/heptio-images/ks-guestbook-demo:0.1
...
Warning Unhealthy 4s (x10 over 76s) kubelet Readiness probe failed: Get "http://10.244.2.24:80/": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
#
for i in worker worker2; do echo ">> node $i <<"; docker exec -it myk8s-$i ctr -n k8s.io image list --quiet | grep -i guestbook; echo; done
#
docker pull gcr.io/heptio-images/ks-guestbook-demo:0.1
gcr.io/heptio-images/ks-guestbook-demo:0.1
gcr.io/heptio-images/ks-guestbook-demo@sha256:fe18e00a6aeece16b5b2f77a32ee60929e8a60e27c71df8df66bf804f5677f47
#
docker pull gcr.io/heptio-images/ks-guestbook-demo:0.1
docker images
docker inspect gcr.io/heptio-images/ks-guestbook-demo:0.1 | grep -i arch
"Architecture": "amd64",
- 삭제 : Cascading deletion - app of app application과 모든 application이 삭제 - Docs
kubectl delete applications -n argocd apps
▶ Non cascade를 선택하면 app of app application만 삭제되고 다른 application은 유지 Ignoring differences in child applications

# you need to create and sync your parent app
cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: apps
namespace: argocd
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
EOF
▶ [CNKCD2024] 선언형으로 만드는 멀티 클러스터 - Cluster API 와 App of Apps 패턴 (문지현) , PDF
7. Argo Rollout
▶ Argo Rollouts 소개 : k8s 고급 배포 도구, Kubernetes Progressive Delivery Controller - Docs
[ 아키텍쳐 구성도 ]

- Argo Rollouts : Argo Rollouts is a Kubernetes controller and set of CRDs which provide advanced deployment capabilities such as blue-green, canary, canary analysis, experimentation, and progressive delivery features to Kubernetes.
- Argo Rollouts는 Ingress Controller 및 Service Meshes와 통합되어 트래픽 형성 기능을 활용하여 업데이트 중에 트래픽을 점진적으로 새 버전으로 전환합니다. 또한 Rollouts는 다양한 공급업체의 메트릭을 쿼리하고 해석하여 주요 KPI를 검증하고 업데이트 중에 자동화된 프로모션 또는 롤백을 실행할 수 있습니다.
- Why Argo Rollouts? : 롤링업데이트만으로는 부족한 기능
- The native Kubernetes Deployment Object supports the RollingUpdate strategy which provides a basic set of safety guarantees (readiness probes) during an update. However the rolling update strategy faces many limitations:
- 출시 속도에 대한 몇 가지 제어 기능 Few controls over the speed of the rollout
- 새 버전으로의 트래픽 흐름**(카나리)을 제어할 수 없음** Inability to control traffic flow to the new version
- Readiness probes 는 ‘복잡하고, 스트레스 또는 일회성 점검’에 적합하지 않습니다 Readiness probes are unsuitable for deeper, stress, or one-time checks
- 업데이트를 확인하기 위해 외부 메트릭을 쿼리할 수 없습니다 No ability to query external metrics to verify an update
- 진행을 중지할 수는 있지만 업데이트를 자동으로 중단하고 롤백할 수는 없습니다 Can halt the progression, but unable to automatically abort and rollback the update
- The native Kubernetes Deployment Object supports the RollingUpdate strategy which provides a basic set of safety guarantees (readiness probes) during an update. However the rolling update strategy faces many limitations:
- Controller Features
A. Blue-Green update strategy

B. Canary update strategy

- 세분하고 가중치를 통한 점진전 트래픽 전환 Fine-grained, weighted traffic shifting
- 자동 롤백 및 프로모션 Automated rollbacks and promotions
- 수동 적용 Manual judgement
- 사용자 지정 가능한 메트릭 쿼리와 분석 Customizable metric queries and analysis of business KPIs
- 입력 컨트롤러 통합 Ingress controller integration: NGINX, ALB, Apache APISIX
- 서비스 메시 통합 Service Mesh integration: Istio, Linkerd, SMI
- 여러 제공업체의 동시 사용 Simultaneous usage of multiple providers: SMI + NGINX, Istio + ALB, etc.
- 메트릭 제공업체 통합 Metric provider integration: Prometheus, Wavefront, Kayenta, Web, Kubernetes Jobs, Datadog, New Relic, Graphite, InfluxDB
▶ Argo Rollouts 설치 및 Sample 테스트 - Docs
Step1. Argo Rollouts 설치
# 네임스페이스 생성 및 파라미터 파일 작성
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
# Argo rollouts 대시보드 접속 주소 확인
echo "http://127.0.0.1:30003"
open "http://127.0.0.1:30003"
Step2. Deploying a Rollout
- 먼저, 롤아웃 리소스와 해당 롤아웃을 타겟으로 하는 Kubernetes 서비스를 배포합니다.
- 이 가이드의 예제 롤아웃은 카나리아 업데이트 전략을 사용하여 트래픽의 20%를 카나리아로 보내고, 이어서 수동 프로모션을 실시하며, 마지막으로 업그레이드의 나머지 기간 동안 점진적으로 자동 트래픽이 증가합니다.
- 이 동작은 롤아웃 사양의 다음 부분에서 설명됩니다:
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 --watch
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
...
"image": "argoproj/rollouts-demo:blue"
...

☞ default 네임스페이스 선택 → rollout-demo 클릭
- 모든 롤아웃의 초기 생성물은 업그레이드가 발생하지 않았기 때문에 즉시 복제본을 100%로 확장합니다(카나리 업그레이드 단계, 분석 등 생략).
- Argo 롤아웃 쿠벡틀 플러그인을 사용하면 롤아웃 및 관련 리소스(ReplicaSets, Pods, AnalysisRuns)를 시각화할 수 있으며, 발생하는 실시간 상태 변경 사항을 표시할 수 있습니다. 롤아웃이 배포되는 동안 롤아웃을 확인하려면 플러그인에서 get rollout --watch 명령을 실행하세요
Step3. Updating a Rollout
- 다음은 업데이트를 수행할 시간입니다. 배포와 마찬가지로 Pod 템플릿 필드(spec.template)를 변경하면 새로운 버전(즉, ReplicaSet)이 배포됩니다.
- 롤아웃 업데이트에는 롤아웃 사양을 수정하고 일반적으로 컨테이너 이미지 필드를 새 버전으로 변경한 다음 새로운 매니페스트에 대해 kubectl 적용을 실행하는 작업이 포함됩니다.
- 편의상 롤아웃 플러그인은 실시간 롤아웃 객체에 대해 다음 단계를 수행하는 설정 이미지 명령을 제공합니다. 다음 명령을 실행하여 롤아웃 데모 롤아웃을 컨테이너의 "yellow" 버전으로 업데이트합니다
# Run the following command to update the rollouts-demo Rollout with the "yellow" version of the container:
KUBE_EDITOR="nano" kubectl edit rollouts rollouts-demo
..
- image: argoproj/rollouts-demo:yellow
...
#
kubectl get rollout --watch
# 파드 label 정보 확인
watch -d kubectl get pod -l app=rollouts-demo -owide --show-labels

- 롤아웃 업데이트 중에 컨트롤러는 롤아웃의 업데이트 전략에 정의된 단계를 진행합니다.
- 예제 롤아웃은 카나리아의 트래픽 가중치를 20%로 설정하고, 롤아웃을 일시 중지/촉진하기 위한 사용자 조치가 취해질 때까지 롤아웃을 무기한 일시 중지합니다. 이미지를 업데이트한 후 일시 중지 상태에 도달할 때까지 롤아웃을 확인하세요.
- 데모 롤아웃이 두 번째 단계에 도달하면 플러그인을 통해 롤아웃이 일시 중지된 상태이며, 이제 5개의 복제본 중 1개가 포드 템플릿의 새 버전을 실행하고 있고, 5개의 복제본 중 4개가 이전 버전을 실행하고 있음을 알 수 있습니다. 이는 set Weight: 20로 정의된 20%에 해당합니다.
Step4. Promoting a Rollout
- 이제 롤아웃이 일시 중지된 상태입니다. 롤아웃이 지속 시간 없이 일시 중지 단계에 도달하면 재개/추진될 때까지 무기한 일시 중지된 상태로 유지됩니다. 롤아웃을 다음 단계로 수동으로 승격하려면 플러그인의 promote 명령을 실행합니다:
# 아래 입력 혹은 UI에서 Promote Yes 클릭
## kubectl argo rollouts promote rollouts-demo
# 정보 확인
kubectl get rollouts rollouts-demo -o json | grep rollouts-demo
watch -d kubectl get pod -l app=rollouts-demo -owide --show-labels
- 프로모션 후 롤아웃은 나머지 단계를 실행합니다. 예제의 남은 롤아웃 단계는 완전히 자동화되어 있으므로 롤아웃은 새 버전으로 완전히 전환될 때까지 단계를 완료합니다. 모든 단계가 완료될 때까지 롤아웃을 확인하세요
- 모든 단계가 성공적으로 완료되면 새로운 ReplicaSet은 "안정적인" ReplicaSet으로 표시됩니다. 업데이트 중에 실패한 카나리아 분석을 통해 자동으로 또는 사용자가 수동으로 롤아웃을 중단하면 롤아웃은 다시 "안정적인" 버전으로 돌아갑니다.
[ 요약 - Summary ]
- 이 기본 예제의 롤아웃은 트래픽을 라우팅하기 위해 입력 컨트롤러나 서비스 메시 제공자를 사용하지 않았습니다. 대신, 일반적인 Kubernetes 서비스 네트워킹(즉, kube-proxy)을 사용하여 새로운 복제본 수와 이전 복제본 수의 가장 가까운 비율을 기준으로 대략적인 카나리아 가중치를 달성했습니다. 그 결과, 이 롤아웃은 새로운 버전을 실행하기 위해 5개의 포드 중 1개를 확장함으로써 최소 카나리아 가중치를 20%까지만 달성할 수 있다는 한계가 있었습니다. 훨씬 더 세밀한 카나리아를 달성하기 위해서는 입력 컨트롤러나 서비스 메시가 필요합니다.
- 트래픽 라우팅 가이드 중 하나를 따라가면 Argo Rollouts가 네트워킹 제공업체를 어떻게 활용하여 보다 발전된 트래픽 형성을 달성할 수 있는지 확인할 수 있습니다.
[ 자원 정리 ] * 실습 후, 모든 자원 삭제!!
docker compose down --volumes --remove-orphans && kind delete cluster --name myk8s
[ 마무리 ]
이번 시간은 docker 기반의 kind cluster 구성 후, Jenkins 및 ArgoCD pipeline 구성 및 연계 실습을 진행하였다. 연계 환경 구성 시, 환경 설정 등 오류를 많이 겪어서 그런지 초반에 좀 힘들었지만, 차근 차근 정리해가면서 정리를 할 수 있었다. 서로 다른 솔루션의 자동화를 위해 Webhook 을 이용한 Triggering 으로 ArgoCD 자동 배포까지 진행되는 과정을 따라가며 자세히 살펴 볼 수 있는 시간이어서 유익했던 것 같다. 실무에 구축된 CICD 배포라인을 이해하는 데 좋은 시작점으로 글을 읽는 분들도 해당 관점에서 접근해 보면 조금은 '배포 자동화' 라는 부분에 대해 자신감을 갖을 수 있지 않을까 싶다.
[ 참고 링크 모음 ]
[ 도전 과제 모음 ]
도전과제1 : Jenkins 를 AWS EKS에 In-Cluster 에 설치(파드로 기동)해서 사용해보자 - KrBlog
도전과제2 : Gogs 를 AWS EKS에 In-Cluster 에 설치(파드로 기동)해서 사용해보자
도전과제3 : Gogs 대신 GitHub Private Repo 를 생성해서 사용해보자
도전과제4 : kind 대신 AWS EKS를 사용해서 실습 진행해보자
도전과제5 : Docker Hub 저장소 대신 AWS ECR 저장소를 사용해서 실습 진행해보자
도전과제6 : 디플로이먼트에 파드 컨테이너 이미지에 latest 사용해서 배포 후 이미지 버전 업데이트 후 적용 방안? (예. imagePullPolicy:Always) - Docs
도전과제7 : Multi-Arch(CPU) 컨테이너 이미지를 빌드할 수 있게 Jenkins Pipeline 을 작성(업데이트) 해보자
도전과제8 : Jenkins 에 Gogs Webhook 대신 범용 Webhook 플러그인를 사용해보자
도전과제9 ArgoCD 에 User 에 API Key 발급 후 ArgoCD API(SYNC/DELETE)를 호출하여 Trigger 동작 설정 - Docs
도전과제10 ArgoCD에 HPA를 포함하는 Sample Deployment 를 배포해보고 App 생성 시 추가 설정을 고려해보자
도전과제11 AWS EKS 환경에서 AWS ECR 과 GitHub Private Repo 를 사용하여 위 과정과 유사한 Full CI/CD를 구성해보자!
도전과제12 ArgoCD Image Updater 를 이용하여 dev-app Repo 에 컨테이너 이미지 버전 업그레이드 시 자동 동작 설정 해보기
도전과제13 Argo CD ApplicationSet 으로 배포해보기 - Docs , Generating Applications with ApplicationSet - Docs
도전과제14 ArgoCD Autopilot: ArgoCD 및 GitOps 환경 자동 구축해보기 - Docs , 장성필님 - krBlog , 악분님 (배경소개) - Blog
도전과제15 Argo Rollout 시, Ingress (or AWS ALB)나 Gateway API를 활용해보자 - Docs , Blog , Docs2 , ALB
도전과제16 Argo Rollout 에 분석을 통한 점진적 전환 ‘Analysis & Progressive Delivery’ 기능을 테스트 해보자 - Docs
도전과제17 [GitOps Bridge] Argo CD on Amazon EKS 배포를 해보자 - Link
도전과제18 [GitOps Bridge] ArgoCD Multi-Cluster Hub & Spoke - Link , Gitub
'AEWS' 카테고리의 다른 글
AEWS 9주차 - EKS Upgrade [AWS Workshop] (0) | 2025.04.01 |
---|---|
AEWS 7주차 - EKS Mode/Nodes (Fargate, Auto-mode) (0) | 2025.03.17 |
AEWS - 6주차 EKS Security (1) | 2025.03.11 |
AEWS 5주차 - EKS Autoscaling (0) | 2025.03.08 |
AEWS 4주차 - EKS Observability (0) | 2025.02.24 |