이 글은 CloudNet@팀의 AWS EKS Workshop Study(AEWS) 3기 스터디 내용을 바탕으로 작성되었습니다.
AEWS는 CloudNet@의 '가시다'님께서 진행하는 스터디로, EKS를 학습하는 과정입니다.
EKS를 깊이 있게 이해할 기회를 주시고, 소중한 지식을 나눠주시는 가시다님께 다시 한번 감사드립니다.
이 글이 EKS를 학습하는 분들께 도움이 되길 바랍니다.

[AWS EKS] Windows에서 EKS 클러스터 배포 및 Network 실습 환경 구성 - 2. Windows 환경 설정 (WSL2 + Ubuntu 24.04)
# 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



# 기본 사용자 디렉터리 이동
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

⚠️ Jenkins와 Gogs를 배포하기 전, 반드시 kind를 먼저 설치하여 kind 네트워크가 생성되어 있어야 합니다.
# 작업 디렉토리 생성 후 이동
mkdir cicd-labs
cd cicd-labs
# cicd-labs 작업 디렉토리 IDE(VSCODE 등)로 열어두기
# docker network 확인 : kind 를 사용
docker network ls
# 출력 예시
...
7e8925d46acb kind bridge local
...
# docker-compose.yaml 파일 작성
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
kind 네트워크를 external 네트워크로 명시했기 때문에, 해당 네트워크가 반드시 존재해야 합니다.
# 컨테이너 배포 및 상태 확인
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



# 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





ifconfig eth0

위에서 inet 항목의 IP 주소(ex. 172.23.129.5)를 사용하여 Jenkins URL을 다음과 같이 설정합니다: http://172.23.129.5:8080/
이 설정을 통해 다른 장치에서 동일 네트워크 상의 Jenkins에 접근할 수 있습니다.





https://daniel00324.tistory.com/17
# Jenkins 컨테이너 내부에 도커 실행 파일 설치
docker compose exec --privileged -u root jenkins bash
-----------------------------------------------------
# 현재 사용자 정보 확인
id
# Docker 공식 GPG 키 등록
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
# Docker 저장소 설정
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
# Docker CLI 및 유틸리티 설치
apt-get update && apt install docker-ce-cli curl tree jq yq -y
# 설치 확인
docker info
docker ps
which docker # /usr/bin/docker 등 위치 확인
# 호스트의 Docker 소켓(/var/run/docker.sock)은 root 또는 docker 그룹에 속한 사용자만 접근 가능
# Jenkins 컨테이너 내부에서 root가 아닌 jenkins 유저도 docker를 실행할 수 있도록 권한을 부여
# docker 그룹 생성 및 GID 지정
groupadd -g 1001 -f docker # Windows WSL2(Container) >> cat /etc/group 에서 docker 그룹ID를 지정 (cat /etc/group | grep docker 명령어를 통해 실제 GID를 확인 후 호스트의 도커 그룹 ID와 동일하게 적용)
# 도커 소켓의 그룹을 docker로 설정
chgrp docker /var/run/docker.sock
# 권한 확인
ls -l /var/run/docker.sock
# jenkins 사용자에게 docker 그룹 권한 추가
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 포함 확인
docker compose exec jenkins docker info # Docker 데몬 정보 출력
docker compose exec jenkins docker ps # 현재 컨테이너 목록 출력



=> Windows WSL2(Container) 상의 docker group ID인 989로 설정
=> 989는 WSL2 호스트 (Ubuntu) 시스템에서 도커 설치 시 자동으로 생성된 GID


📌 참고 자료:
WSL2 환경에서 기동한 Gogs 컨테이너는 아래 주소를 통해 웹 브라우저에서 접근 가능합니다.
http://127.0.0.1:3000/install
위 주소는 Windows 브라우저에서 접근하며, 설치 마법사가 자동으로 실행됩니다.
Gogs 설치 마법사에서 다음 값을 설정합니다.
SQLite3http://<각자 자신의 WSL2 Ubuntu eth0 IP>:3000/ifconfig eth0 명령어로 WSL2 IP를 확인한 뒤, 해당 IP를 입력합니다.maindevops (닉네임 사용)qwe123모든 항목 입력 후, 페이지 하단의 [Gogs 설치하기] 버튼을 클릭합니다.



CI/CD에서 Gogs 저장소에 접근할 때 인증 수단으로 사용할 Access Token을 생성합니다.
Your Settings 메뉴 진입
Applications 선택Generate New Token 클릭devops 입력
Generate Token 버튼 클릭
➡ 생성된 토큰은 한 번만 표시되므로 반드시 메모해두어야 합니다.
이 토큰은 Jenkins 등 외부 시스템에서 Gogs 인증을 대신해 사용하는 비밀번호 대체용으로 활용됩니다.
CI/CD 실습을 위한 두 개의 Private 저장소를 생성합니다.
완료 후, [Create Repository] 클릭 → 저장소 생성 및 주소 확인


완료 후, [Create Repository] 클릭 → 저장소 생성 및 주소 확인


📍 Git 작업 위치
# (옵션) GIT 인증 정보 초기화
git credential-cache exit
#
git config --list --show-origin
# 환경 변수 설정
# Gogs Access Token 입력
TOKEN=<각자 Gogs Token>
TOKEN=3e3882af4b7b732cc1f7a313bc98fa09173ef2bc
MyIP=<각자 자신의 PC IP> # Windows (WSL2) 사용자는 자신의 WSL2 Ubuntu eth0 IP 입력 할 것!
MyIP=192.168.254.127
# 저장소 Clone
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 로컬 설정
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 # 이후 인증 정보를 저장하여 입력 없이 Push 가능
git --no-pager config --local --list
cat .git/config
# 저장소 상태 확인
git --no-pager branch
git remote -v
# server.py 애플리케이션 파일 작성
# / 요청 시 현재 시간과 서버 호스트명을 출력
# /healthz 경로는 헬스 체크용으로 Healthy 응답 반환
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
# Git 커밋 및 원격 저장소에 푸시
tree # 파일 구조 확인 (옵션)
git status # 변경 파일 확인
git add . # 전체 파일 스테이징
git commit -m "Add dev-app" # 커밋
git push -u origin main # 원격 저장소에 푸시
...





server.py), Dockerfile, 버전(VERSION) 파일이 정상적으로 반영되어야 합니다.



웹 브라우저에서 https://hub.docker.com 접속 후, 본인의 Docker Hub 계정으로 로그인합니다.
Repositories 선택 → Create repository 클릭dev-appPrivate 선택Private 저장소로 설정하면 도커 이미지가 외부에 노출되지 않습니다.


Docker Hub는 보안 강화를 위해 일반 비밀번호 대신 Personal Access Token을 권장합니다. 해당 토큰은 docker login 명령어 또는 자동화된 배포 파이프라인에서 인증 수단으로 사용됩니다.
우측 상단 프로필 아이콘 클릭 → Account Settings 메뉴 진입


k8s testRead, Write, Delete 모두 활성화
생성된 토큰은 한 번만 표시되므로 반드시 메모해둬야 합니다.
Windows (WSL2) 환경에서 kind를 이용해 Kubernetes 클러스터를 배포하는 과정을 정리한 내용입니다.
# 클러스터 배포 전 확인
docker ps
# WSL2 Ubuntu eth0 IP를 지정
ifconfig eth0
MyIP=<각자 자신의 WSL2 Ubuntu eth0 IP>
MyIP=172.19.21.65
# cicd-labs 디렉터리에서 kind 클러스터 설정 파일 작성
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 네트워크 및 컨테이너 정보
# kind 는 별도 도커 네트워크 생성 후 사용 : 기본값 172.18.0.0/16
docker network ls
docker inspect kind | jq
172.18.0.0/16

# 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"



다음은 Jenkins 설정을 위한 플러그인 설치 및 자격증명 등록 절차를 정리한 설명입니다. Jenkins를 활용한 CI/CD 파이프라인 구성에 필수적인 단계로, 실습 환경에서는 Gogs와 Docker Hub, Kubernetes(kind)와의 연동을 위해 각각의 인증 정보를 등록하게 됩니다.
| 플러그인명 | 용도 | 설명 |
|---|---|---|
| Pipeline Stage View | 파이프라인 시각화 | 파이프라인 실행 결과를 그래픽으로 보여주는 UI 제공 🔗 Docs |
| Docker Pipeline | Docker 사용 | Jenkins Pipeline에서 Docker 빌드/테스트/사용을 가능하게 해주는 플러그인 🔗 Docs |
| Gogs | Git Webhook 연동 | Gogs 저장소에서 Jenkins Job 트리거 가능하도록 하는 Webhook 수신 플러그인 🔗 Docs |
http(s)://<< jenkins-server >>/gogs-webhook/?job=<<jobname>>






Jenkins에서는 외부 시스템과의 연동을 위해 Credentials(자격증명) 을 미리 등록해야 합니다. 등록된 자격증명은 파이프라인 내부에서 ID를 통해 참조할 수 있습니다.
Jenkins 웹 UI 상단 메뉴 → [Jenkins 관리] → [Credentials] → [Global] (전역 범위) → [Add Credentials]

gogs-crd| 항목 | 값 |
|---|---|
| Kind | Username with password |
| Username | devops |
| Password | Gogs에서 발급한 Access Token |
| ID | gogs-crd |

dockerhub-crd| 항목 | 값 |
|---|---|
| Kind | Username with password |
| Username | Docker Hub 계정명 |
| Password | Docker Hub 비밀번호 또는 Personal Access Token |
| ID | dockerhub-crd |

k8s-crd| 항목 | 값 |
|---|---|
| Kind | Secret file |
| File | kubeconfig 파일 (직접 업로드) |
| ID | k8s-crd |

💡 파일 준비 방법 (Windows 사용자)
cat ~/.kube/config 명령어로 kubeconfig 내용을 복사합니다.kube-config 파일로 저장한 뒤 Jenkins에 업로드합니다.

이 자격증명들은 Jenkins Pipeline 내에서 다음과 같이 참조될 수 있습니다:
withCredentials([usernamePassword(credentialsId: 'gogs-crd', ...)])
withCredentials([file(credentialsId: 'k8s-crd', ...)])
다음은 Jenkins에서 Pipeline Item을 생성하고 Docker 이미지를 빌드 및 푸시하는 파이프라인을 구성하는 과정을 정리한 설명입니다. 본 파이프라인은 Gogs에 저장된 애플리케이션 소스를 Git으로 가져와 Docker 이미지를 빌드하고, Docker Hub에 푸시하는 과정까지 자동화하는 데 목적이 있습니다.
pipeline-ci 입력
파이프라인 스크립트에서 다음 항목들은 사용자 환경에 맞게 반드시 수정해야 합니다.
gasida/dev-apphttp://192.168.254.127:3000/devops/dev-app.gitgogs-crddockerhub-crd전체 스크립트 예시
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-ci Job 진입 → [지금 빌드] 버튼 클릭


VERSION 파일 읽기 성공 (Version found: 0.0.1 등)
latest: digest: sha256:...)
dev-app 저장소 확인0.0.1, latest 등의 태그가 정상 등록되어 있어야 합니다
timeserver 애플리케이션을 2개의 파드로 배포합니다.
# 디플로이먼트 오브젝트 배포 : 리플리카(파드 2개), 컨테이너 이미지 >> 아래 도커 계정 부분만 변경해서 배포해보자
DHUSER=<도커 허브 계정명>
DHUSER=gasida
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 2
selector:
matchLabels:
pod: timeserver-pod
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: docker.io/$DHUSER/dev-app:0.0.1
livenessProbe:
initialDelaySeconds: 30
periodSeconds: 30
httpGet:
path: /healthz
port: 80
scheme: HTTP
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
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




# 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






# 서비스 생성
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


# VERSION 변경 : 0.0.2
# server.py 변경 : 0.0.2
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main




Docker Hub에 새로운 태그로 이미지가 Push 됩니다.

# 파드 복제복 증가
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





다음은 Gogs Webhooks를 설정하여 Jenkins Pipeline을 자동으로 트리거하는 방법을 정리한 설명입니다. 이 설정을 통해 Gogs 저장소에 변경 사항(Push) 이 발생하면 Jenkins Job이 자동으로 실행되도록 연동할 수 있습니다.
기본적으로 Gogs는 외부 네트워크 접근 제어를 통해 Webhook 요청을 제한할 수 있습니다. 따라서, Jenkins가 설치된 호스트(예: WSL2 Ubuntu IP)가 Webhook 허용 대상임을 명시해야 합니다.
VSCode에서 Docker Extension을 설치합니다.

컨테이너 내부의 설정 파일 /data/gogs/conf/app.ini 파일에서 다음 항목을 수정합니다.
[security]
INSTALL_LOCK = true
SECRET_KEY = j2xaUPQcbAEwpIu
LOCAL_NETWORK_ALLOWLIST = 192.168.254.127
LOCAL_NETWORK_ALLOWLIST 항목에 자신의 WSL2 Ubuntu IP 주소를 입력합니다.ifconfig eth0 명령어로 확인 가능
설정 변경 후, 변경 사항을 적용하기 위해 컨테이너를 재시작합니다.
docker compose restart gogs

이제 특정 저장소에서 Jenkins Pipeline Job을 트리거할 수 있도록 Webhook을 설정합니다.
Gogs 저장소 페이지에서 [Settings] → [Webhooks] → [Gogs] 클릭

| 항목 | 설정값 |
|---|---|
| Payload URL | http://<자신의 집 IP>:8080/gogs-webhook/?job=SCM-Pipeline/ |
| Content Type | application/json |
| Secret | qwe123 |
| Trigger Event | Just the push event |
| Active | 체크(On) |


Webhooks를 통해 Jenkins에서 자동으로 빌드가 트리거되기 위해서는 Gogs 저장소와 연동된 Jenkins Pipeline Job이 먼저 구성되어 있어야 합니다.

by ChatGPT

http://<mac IP>:3000/<Gogs 계정명>/dev-app ← .git 은 제거http://<mac IP>:3000/<Gogs 계정명>/dev-app)
# VERSION 파일 : 0.0.3 수정
# server.py 파일 : 0.0.3 수정

# Jenkinsfile 빈 파일 작성
touch Jenkinsfile
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."
}
}
}

변경 사항 Git 저장소에 Push
git add . && git commit -m "VERSION $(cat VERSION) Changed" && git push -u origin main

Gogs 저장소에 코드 변경 및 Jenkinsfile을 Push하면, Webhook을 통해 Jenkins Job이 자동으로 트리거됩니다.
Gogs Webhook 기록 확인

정상 작동 시 HTTP 200 OK 상태로 표시됩니다.
Jenkins 빌드 트리거 확인

Docker Hub 저장소 확인

이미 배포된 Deployment 리소스를 업데이트하여 새로운 버전의 이미지를 사용하도록 지정합니다.
# 신규 버전 적용
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"


Jenkins에서 Kubernetes 자원을 직접 다루기 위해 Jenkins 컨테이너에 kubectl과 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


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에 Push
git add . && git commit -m "Add echo server yaml" && git push -u origin main

kubectl delete deploy,svc timeserver
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
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"
}
}
}
}
}
}











kubectl delete deploy echo-server-blue echo-server-green && kubectl delete svc echo-server-service
| 항목 | 단일 저장소 (통합) | 분리 저장소 (개발 / 배포) |
|---|---|---|
| 장점 | 관리 단순, 연계 편리, 코드 변경과 배포 동기화 쉬움 | 책임 분리, 배포 이력 독립적 관리, 보안 강화 |
| 단점 | 권한 분리 어려움, 배포 코드 변경 시 개발 코드 영향을 받을 수 있음 | 초기 구성 복잡, 연동 로직 추가 필요, 커뮤니케이션 비용 증가 |
# 네임스페이스 생성 및 파라미터 파일 작성
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
# AppProject 리소스 조회
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 접속




Argo CD 웹 접속 확인

User Info → UPDATE PASSWORD 로 admin 계정 암호 변경 (qwe12345)

Git 저장소 연결 (ops-deploy)
http://<자신의 집 IP>:3000/devops/ops-deploy http://192.168.254.127:3000/devops/ops-deploy

#
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










#
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



















⇒ GitOps를 위해서는, 반드시 단일 진실 공급원(Single Source Of Trush, SSOT)를 통해서 관리를 해야합니다.
#
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



SYNC 클릭 → SYNCHRONIZE 클릭


#
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







#
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







#
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





#
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






kubectl delete applications -n argocd dev-nginx
#
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



#
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




ArgoCD 기존 방식

https://kmaster.tistory.com/85
ArgoCD Image Updater

https://kmaster.tistory.com/85

[출처] 책 GitOps Cookbook - https://product.kyobobook.co.kr/detail/S000214781090



https://malwareanalysis.tistory.com/478

#
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
# 확인
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",
Argo Rollouts는 Kubernetes의 기본 롤링 업데이트 전략보다 더 정교하고 유연한 배포 방식을 제공합니다. Ingress Controller나 Service Mesh와의 통합을 통해 트래픽을 점진적으로 새로운 버전으로 전환할 수 있으며, 다양한 외부 메트릭을 기반으로 KPI를 확인하여 자동으로 배포를 승격(promote)하거나 롤백할 수 있도록 지원합니다.
Kubernetes의 기본 Deployment 오브젝트는 RollingUpdate 전략을 제공하지만 다음과 같은 한계가 존재합니다:
출시 속도 제어 기능 부족: 배포 속도를 세밀하게 제어할 수 없습니다.
트래픽 제어 불가: 새 버전에 대한 트래픽을 점진적으로 유도하는 Canary 배포를 직접 제어할 수 없습니다.
Readiness Probe 한계: 복잡하거나 부하 테스트(Stress Test), 일회성 검증에는 적합하지 않습니다.
외부 메트릭 확인 불가: Prometheus 등의 외부 모니터링 시스템을 쿼리하여 배포 성공 여부를 판단할 수 없습니다.
자동 롤백 기능 없음: 문제가 발생하면 수동으로 중단은 가능하나, 자동으로 배포를 중지하거나 롤백할 수 없습니다.

https://argoproj.github.io/argo-rollouts/architecture/
Kubernetes 클러스터를 만드는 과정부터 GitOps를 통해 모든 것을 관리 - Github
