시작하며
개발자라면 누구나 CI/CD라는 용어를 들어봤을 것입니다. 저도 AWS 프리티어를 이용해 간단히 CI/CD 파이프라인을 구축해본 경험만 있고, 사내에서는 이미 구축된 시스템을 사용해왔기 때문에 직접적인 경험은 부족했습니다. 이 글에서는 사내에서 신규 프로젝트의 CI/CD 파이프라인을 구축한 경험을 공유하고자 합니다. 저처럼 경험이 부족한 분들에게 간접적인 경험을 통해 조금이나마 도움이 되길 바랍니다.
실제 구축하면서 여러 차례 Git Action Failure가 발생하여 구글링을 통해 해결하였으며, 모든 설정 파일을 완벽하게 외우지는 못하지만, 대략적인 그림이 그려진다면 다른 환경에서도 작업이 가능할 것이라 생각됩니다.
먼저 CI/CD란 무엇인가?
대표적인 CI/CD 툴로는 Jenkins, GitHub Actions, Travis CI 등이 있으며, 필자는 Git Action을 선택하였습니다.
- Git Action 장점
- GitHub Actions는 GitHub와 통합되어 있어 별도의 서버 설치가 필요 없습니다.
- GitHub Actions는 간편한 설정과 강력한 기능을 제공하여 빠르게 CI/CD 파이프라인을 구축할 수 있습니다.
그 외에 Docker 컨테이너 관리를 위해 AWS의 ECR을 선택하였고, 안전한 관리를 위해 Kubernetes 클러스터 서비스인 EKS를 활용하였습니다.
- ECR의 장점
- AWS 내에서 Docker 컨테이너 이미지를 저장하고 관리하기 위한 서비스로, AWS의 다른 서비스들과의 통합성이 뛰어납니다.
- EKS의 장점
- EKS는 고가용성, 확장성, 보안성 등의 특징을 제공하여 안정적인 운영 환경을 제공합니다.
필자는 위 장점들 때문에 해당 기술스택을 사용하였습니다.
1. Git Secret 설정하기
Git Repository의 Settings에서 GitHub Actions에 필요한 환경 변수를 설정할 수 있습니다. 민감한 정보를 보호하기 위해, 환경 변수는 Secret에 Key-Value 형태로 등록합니다.
- 환경 변수 등록
- 시크릿의 이름(Key)과 값을 입력합니다.
- Secret을 등록후 Value 값을 확인할 수는 없다.
이렇게 등록된 시크릿은 GitHub Actions 워크플로우에서 참조하며, 민감한 정보가 노출되지 않도록 보호됩니다.
2. DockerFile 작성
Git Action을 통해 Docker Image를 만들 DockerFile을 작성합니다. 아래는 예제 Dockerfile입니다.
# 빌드 스테이지
FROM adoptopenjdk/openjdk8 AS build
# 애플리케이션 파일 복사
COPY gradlew .
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY lib/nsso-agent-15.jar .
COPY src src
COPY lib lib
# 권한 설정 및 빌드
RUN chmod +x ./gradlew
RUN ./gradlew bootJar
# 실행 스테이지
FROM adoptopenjdk/openjdk8:latest
LABEL maintainer="myProject"
VOLUME /tmp
# 환경 변수 설정
ARG SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-dev}
ENV SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-dev}
RUN echo ${SPRING_PROFILES_ACTIVE}
# 빌드된 JAR 파일 복사
COPY --from=build build/libs/*.jar app.jar
# 타임존 설정
ENV TZ=Asia/Seoul
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 사용자 및 권한 설정
RUN groupadd -r springboot && useradd -r -g springboot springboot
RUN mkdir -p /logs/sso && chown -R springboot:springboot /logs/sso
# 사용자 변경
USER springboot
# 포트 설정
EXPOSE 17010
# 애플리케이션 실행
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
- 실행 스테이지
- 빌드된 JAR 파일을 실행 환경으로 복사하고, 환경 변수 및 타임존 설정을 수행합니다.
- 애플리케이션을 실행할 사용자와 권한을 설정한 후, JAR 파일을 실행합니다.
이 Dockerfile을 사용하여 GitHub Actions에서 Docker 이미지를 빌드하고, 이를 Kubernetes 클러스터에 배포할 수 있습니다.
3. Kubernetes File 작성
애플리케이션을 Kubernetes 클러스터에 배포하기 위한 YAML 파일을 작성합니다.
Deployment 파일 (deployment.yaml)
apiVersion: v1
kind: Namespace
metadata:
namespace: myProject
name: myProject
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: myProject
name: myProject
spec:
replicas: 1
selector:
matchLabels:
app: myProject
template:
metadata:
labels:
app: myProject
spec:
containers:
- name: myProject
image: kustomization-eks-repository
imagePullPolicy: Always
ports:
- containerPort: 17010
env:
- name: SPRING_PROFILES_ACTIVE
value: dev
- name: AWS_REGION
value: ap-northeast-2
Kustomization 파일 (kustomization.yaml)
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
images:
- name: kustomization-eks-repository
Service 파일 (service.yaml)
apiVersion: v1
kind: Service
metadata:
namespace: myProject
name: myProject
spec:
selector:
app: myProject
ports:
- port: 80
targetPort: 17010
name: http
- Namespace
- Namespace 리소스를 정의하여 myProject라는 네임스페이스를 생성합니다.
- Deployment
- 애플리케이션이 실행될 컨테이너 이미지를 지정하고, 환경 변수를 설정합니다.
- 클러스터 내에서 애플리케이션의 복제본 수(replicas)를 정의합니다.
- Kustomization
- deployment.yaml과 service.yaml 파일을 리소스로 포함하고, 이미지 태그를 latest로 설정합니다.
- Service:
- service.yaml 파일에서 Service 리소스를 정의하여 클러스터 외부에서 애플리케이션에 접근할 수 있도록 합니다.
- selector를 사용하여 Deployment와 연결하고, 포트 매핑을 설정합니다.
이렇게 작성된 Kubernetes 파일들을 사용하여, 애플리케이션을 클러스터에 배포하고 관리할 수 있습니다.
4. Git Action 파일작성
├── .github
│ └── workflows
│ └── pipeline-push-dev.yaml
name: DEV - Deploy to Amazon EKS
on:
push:
branches: [ dev ]
paths-ignore:
- ".github/**"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
EKS_CLUSTER: ${{ secrets.DEV_EKS_CLUSTER_NAME }}
ECR_REPOSITORY: ${{ secrets.DEV_ECR_REPOSITORY }}
APP_NAME: myProject # Application 이름. Image TAG Prefix로 사용 됨
AWS_REGION: ap-northeast-2 # AWS EKS & ECR이 위치한 AWS Region
DEPLOYMENT_NAME: myProject # Kubernetes Deployment 명
EKS_NAMESPACE: myProject
YAML_ENV: kustomize/dev
jobs:
build:
name: Build and Push Docker Image
runs-on: ubuntu-latest
steps:
# 소스 가져오기
- name: Checkout
uses: actions/checkout@v2
# AWS credentials 설정
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
# AWS ECR 로그인
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# sha 난수 생성
- name: Short sha
run: echo "short_sha=`echo ${{ github.sha }} | cut -c1-8`" >> $GITHUB_ENV
# Docker 빌드 및 ECR로 Push 진행
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ env.ECR_REPOSITORY }}
IMAGE_TAG: DEV_${{ env.APP_NAME }}
SHORT_SHA: ${{ env.short_sha }}
run: |-
pwd
ls -al
docker build -t ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}_${SHORT_SHA} -f Dockerfile_DEV .
docker push ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}_${SHORT_SHA}
docker tag ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}_${SHORT_SHA} ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}_latest
docker push ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}_latest
echo "::set-output name=image::${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}_${SHORT_SHA}"
deploy:
needs: build
name: Deploy to DEV Environment
runs-on: ubuntu-latest
steps:
# 소스 가져오기
- name: Checkout
uses: actions/checkout@v2
# AWS credentials 설정
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
# AWS ECR 로그인
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
# sha 난수 생성
- name: Short sha
run: echo "short_sha=`echo ${{ github.sha }} | cut -c1-8`" >> $GITHUB_ENV
# EKS 배포를 위한 Kubeconfig 설정
- name: Setup kubeconfig
id: setup-kubeconfig
env:
AWS_REGION: ${{ env.AWS_REGION }}
EKS_CLUSTER: ${{ env.EKS_CLUSTER }}
run: |-
aws eks --region ${AWS_REGION} update-kubeconfig --name ${EKS_CLUSTER}
# EKS로 배포
- name: Deploy to DEV Environment
id: deploy-eks
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ env.ECR_REPOSITORY }}
IMAGE_TAG: DEV_${{ env.APP_NAME }}
SHORT_SHA: ${{ env.short_sha }}
EKS_NAMESPACE: ${{ env.EKS_NAMESPACE }}
run: |-
cd $YAML_ENV
GitHub Actions 파일을 작성하여 CI/CD 파이프라인을 자동화합니다. 필자는 GitHub Actions 템플릿이 아닌 직접 작성한 Git Action 파일을 사용하여 배포를 수행하는 작업을 구성했습니다. dev 브랜치에 푸시될 때 작업이 수행되며, Docker 이미지를 빌드하고 Amazon ECR에 푸시한 후 Kubernetes 클러스터에 배포합니다.