[CI/CD]3주차 - Jenkins CI + K8S(Kind)
kind 설치 및 클러스터 배포
kind는 docker in docker로 쿠버네티스 클러스터 환경을 구성할 수 있다.
그래서 각 노드는 컨테이너로 이루어져 있다.
로컬 환경에 쿠버네티스 환경을 구성하기 위해 kind 툴을 먼저 설치해 준다.
brew install kind
kubectl도 설치해 준다.
brew install kubernetes-cli
helm도 설치해 준다.
brew install helm
각종 편리한 툴도 설치해 준다.
# 툴 설치
brew install krew
brew install kube-ps1
brew install kubectx
# kubectl 출력 시 하이라이트 처리
brew install kubecolor
echo "alias kubectl=kubecolor" >> ~/.zshrc
echo "compdef kubecolor=kubectl" >> ~/.zshrc
# krew 플러그인 설치
kubectl krew install neat stren
아래 클러스터 배포용 파일을 하나 생성한다.
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
- apiServerAddress: api 서버는 현재 내 PC ip로 지정해 준다.
- control-plane 한 대, worker 두 대 생성해 준다.
kind create cluster --config kind-3node.yaml --name myk8s --image kindest/node:v1.30.6
로 myk8s 이름으로 클러스터를 생성해 준다.
docker ps로 확인해 보면 각 노드는 컨테이너 형태로 구동되어 있으며, control plane은 외부에서 접근할 수 있는 포트로 매핑되어 있다.
kind는 별도 네트워크를 생성해서 사용하고 172.19.0.0/16 대역을 사용하고 있다
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
http://127.0.0.1:30001/#scale=1.5
로 접속하면 된다
Jenkins 설정 : Plugin 설치, 자격증명 설정
Jenkins Dashboard > Jenkins 관리 > Plugins에서 플러그인 세 개를 설치해 준다 (Pipeline Stage View, Docker Pipeline, Gogs)
Jenkins 관리 > Credentials > Globals > Add Credentials에 들어가 아래 세 가지 자격증명도 설정해 준다.
Jenkins Item 생성(Pipeline)
간단하게 git checkout 진행하여 VERSION 파일에 명시된 버전과 latest 버전을 붙여서 docker hub에 이미지 푸시하는 과정이다.
pipeline {
agent any
environment {
DOCKER_IMAGE = '6ain/dev-app'
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.219.100: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."
}
}
}
처음에 credentials를 유저 쪽에 만들어서 authentication failed 에러가 발생했었다. 다시 system 쪽으로 만들어서 배포 성공하였다!
k8s Deploying an application
deployment 1, pod 2개 생성을 위한 yaml파일 하나 작성하자!
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 2
selector:
matchLabels:
pod: timeserver-pod
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: docker.io/$DHUSER/dev-app:0.0.1
EOF
ImagePullBackOff 에러가 발생한다
이벤트를 확인해 보면 Failed to pull image "[docker.io/6ain/dev-app:0.0.1](http://docker.io/6ain/dev-app:0.0.1)": failed to pull and unpack image "[docker.io/6ain/dev-app:0.0.1](http://docker.io/6ain/dev-app:0.0.1)": failed to resolve reference "[docker.io/6ain/dev-app:0.0.1](http://docker.io/6ain/dev-app:0.0.1)": pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed
라는 로그로 권한 문제임을 알 수 있다.
DHUSER와 DHPASS에 계정 정보를 설정하고 secret값을 생성해 준다.
kubectl create secret docker-registry dockerhub-secret \
--docker-server=https://index.docker.io/v1/ \
--docker-username=$DHUSER \
--docker-password=$DHPASS
kubectl get secrets -o yaml로 확인해 보면 인코딩된 값으로 확인할 수 있다
imagePullSecrets:
값을 추가하고 다시 배포해 보자
cat <<EOF | kubectl apply -f -
*apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 2
selector:
matchLabels:
pod: timeserver-pod
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: docker.io/$DHUSER/dev-app:0.0.1
imagePullSecrets:
- name: dockerhub-secret*
EOF
이제 정상적으로 컨테이너를 생성할 수 있다!
curl 테스트할 수 있는 pod를 생성해서 curl 테스트를 해 보니 정상적으로 현재 시간을 출력해 준다
pod를 하나 삭제해 보면 신규 pod가 생성되면서 ip가 변경되는 것을 확인할 수 있었다
pod의 고정 진입점을 만들어 주기 위해 service를 하나 생성해 주자
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: timeserver
spec:
selector:
pod: timeserver-pod
ports:
- port: 80
targetPort: 80
protocol: TCP
nodePort: 30000
type: NodePort
EOF
service는 도메인으로 접속 가능하고
cluster ip로도 접속 가능하다
node port를 통해 외부 접근도 가능하다
service를 통해 각각의 pod에 부하분산도 가능하다
Updating your application
버전 정보를 바꿔서 push해 보자
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"
정상적으로 0.0.2 버전으로 이미지가 교체되었다
항상 최신의 이미지를 가지고 오려면 image:
에 latest
태그를 붙이고 imagePullPolicy: Always
를 추가해 준다.
imagePullPolicy: Always
를 사용함으로써 컨테이너는 캐시된 이미지를 사용하는 것이 아니라 항상 이미지저장소에서 최신 이미지를 가지고 올 수 있다.
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:latest
imagePullPolicy: Always
imagePullSecrets:
- name: dockerhub-secret
EOF
Gogs Webhooks 설정 : Jenkins Job Trigger
Gogs Webhooks를 사용하기 위해 app.ini 파일 수정 후 컨테이너 재기동한다
gogs > data > gogs > conf > app.ini > security 부분 맨 아래에 LOCAL_NETWORK_ALLOWLIST = 내PC IP
를 추가해 준다
Jenkins Item 생성(Pipeline) : item name(SCM-Pipeline)
Gogs Webhook을 사용하기 위해 젠킨스 파이프라인을 하나 생성한다.
project url은 gogs 레포 주소를 넣어주고 Secret은 위 Gogs Webhook Secret 값을 넣어준다
Build Triggers는 Build when a change is pushed to Gogs로 선택해 준다
Pipeline script from SCM으로 선택해서 해당되는 값을 기입해 준다.
로컬로 돌아가 Jenkinsfile을 생성해서 아래 대로 넣어준다.
파이프라인 과정은 위에 ci 파이프라인과 동일하다
pipeline {
agent any
environment {
DOCKER_IMAGE = '6ain/dev-app' // Docker 이미지 이름
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: 'http://192.168.219.100: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."
}
}
}
-
Jenkins 트리거 빌드 확인
-
도커 저장소 확인
-
Gogs WebHook 기록 확인
kubectl set image deployment timeserver timeserver-container=$DHUSER/dev-app:0.0.3
로 이미지를 교체해서 pod에 신규 버전을 적용한다