Github hosted runner에서는 다양한 actions에서 caching 전략을 제공하기 때문에 단순 actions를 사용하는 것만으로 손쉽게 캐싱을 수행할 수 있다.
캐싱은 docker image caching도 아주 쉽게 수행할 수 있다.
이는 buildx에서 --cache-from과 --cache-to의 type 중 gha(github action)란 type을 제공하는데, 아주쉽게 구현 가능하다.
gradle cache 또한 제공하는 actions를 쓰면 된다.
하지만 github hosted runner를 쓰면 다양한 단점이 존재한다.
1. 개발한 코드가 클라우드 상에 배포된다.
2. runner가 argocd 등의 명령어를 수행하기 위해 argocd가 public 망에 존재해야 한다. 만약 방화벽을 제어하려면 수많은 github 네트워크 대역대를 열어줘야만 한다.
3. 1초 실행되는 job 조차 1분 사용한 것으로 간주된다.
4. 3000분 이상 사용할 경우 과금처리된다.
하여 직접 github actions runner를 관리하는 방식인 self hosted runner를 고려하게 되었다.
self hosted runner를 사용하는 방법도 정말 다양한데, 여기에서는 k8s에서 운영하는 방식인 actions runner controller 상에서 캐싱하는 방법을 소개한다.
Actions Runner Controller로 Self Hosted Runner를 동작시키면 기본적으로 Job을 수행시킨 뒤에 기존 Runner Pod는 status code가 0으로 변경되면서 중지되고 다시 시작된다.
아마 여러 Workflow가 다른 Repository 환경 간 Workflow Job을 공유하지 않도록 하지 않기 위함인 것으로 보인다.
이로 인해 당연히 Docker Cache나 Gradle Dependency Cache 또한 사라지는데, 이럴 때에는 어떻게 조치해야 할까?
방안은 여러가지가 있겠지만 한가지 방안으로는 EBS로 PV를 생성하고 CI Runner는 그 PV를 Mount 하여 캐시 데이터를 쓰도록 하는 것이다.
물론 CD Runner는 캐시가 필요하지 않기 때문에 상관없다.
참고로 Runner Pod가 Job이 끝난 뒤에 재시작하지 않도록 설정할 수 있다.
하지만 권장되는 방법은 아니기 때문에 최대한 Volume을 공유하여 사용하는 방법이 좋을 것으로 보인다.
1. 아이디어
[1] Docker는 /var/lib/docker 경로에 image나 network 설정, 볼륨 등의 정보를 저장한다.
[2] Gradle은 ${HOMEDIR}/.gradle , ${HOMEDIR}/.m2 에 Gradle에 의해 캐시 디렉터리가 생성된다.
이 2가지의 아이디어를 가지고 Self Hosted Runner에서도 캐시를 수행할 수 있을 것으로 생각한다.
위 내용 별개로 Docker Image를 캐시하거나 Gradle Dependency Cache 할 수 있는 방법은 정말 다양하다.
Docker는 Image Registry에도 캐시를 저장할 수 있고 특정 Local Path에도 캐시를 저장할 수 있고 s3에도 저장할 수 있다고 한다.
하지만 현재 ECR에 Docker Cache를 저장하는건 지원이 안된다.
Gradle 또한 build.gradle 파일 내에 어느 경로에 Build Cache를 저장할지 등을 정의할 수 있다.
2. 아키텍쳐
캐시 데이터는 CI를 수행하는 Job에서만 필요하다. ArgoCD에 배포하도록 하는 Job에는 굳이 캐시가 필요없다.
따라서 Runner Lable을 2개로 분리하고 CI Runner에만 PVC를 2개 연결해주면 될 것으로 보임
3. 설정
아래 내용을 참고하여 설정을 진행하였다.
https://github.com/actions/actions-runner-controller/issues/847
3.1. EBS 2개 생성
EBS CSI Driver Controller을 설치해둬야 하고, StorageClass에 의해 dynamic하게 volume을 생성하는건 runner가 job을 수행하고 죽게 될 경우 새로운 volume을 생성하기 때문에 static하게 volume을 지정하는 과정이 필요했다.
docker-volume : /var/lib/docker 에 마운트시킬 EBS Volume
runner-volume : /home/runner/.gradle 에 마운트시킬 EBS Volume
주의 : Pod의 AZ와 EBS의 AZ는 C Zone으로 동일하게 맞춰주었다.
3.2. PV, PVC 생성
PV 2개 생성
3.3. RunnerSet 생성
Runner를 구동할 수 있는 기능이 2가지인데, RunnerDeployment와 RunnerSet이다.
RunnerSet은 StatefulSet과 유사하다고 생각하면 되고 RunnerDeployment는 Deployment와 유사하다고 보면 된다.
만약 RunnerDeployment에서 Volume을 세팅하고 싶다면 설정이 살짝 다르다.
nodeAffinity : c zone에만 배포되도록 설정
initContainer : /home/runner/.gradle 경로의 Owner가 기본적으로 Root인데, 이를 수정하기 위한 InitContainer이다.
runner에는 runner-volume을 마운트해주었고,
docker에는 docker-volume을 마운트해주었다.
runner는 Job을 실질적으로 수행시키는 컨테이너이고
docker는 docker daemon을 제공해주는 컨테이너이다.
4. Workflow 테스트
Workflow는 간단하게 아래대로 생성하였다.
ci:
name: Continuous Integration
runs-on: mgmt-test
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
env:
AWS_REGION: ap-northeast-2
with:
role-to-assume: ${{ env.ASSUME_ROLE }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
distribution: 'corretto'
java-version: '11'
- name: Build SpringBoot with Gradle
run: |
./gradlew clean build
- name: Build and Push image to ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: "${{ env.SERVICE_NAME }}-${{ env.ENVIRONMENT }}"
IMAGE_TAG: ${{ github.sha }}
run: |
docker build --build-arg ENVIRONMENT=${{ env.ENVIRONMENT }} \
-t ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }} .
docker push ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
4.1. 첫번째 빌드
Gradle 빌드 : 대략 2m 3s 소요
Docker Image 빌드 : 대략 1m 소요
4.2. /var/lib/docker 만 마운트했을 때
Gradle 빌드 : 그대로 대략 2m 14s 소요
Docker Image 빌드 : 3s 소요
→ Base Image랑 RUN yum update ~~ 하는 부분을 기 저장된 Docker Image Layer를 받아와서 Cache
4.3. /home/runner/.gradle 마운트 추가
Gradle 빌드 : 20s 소요
Docker Image 빌드 : 4s 소요
5. 장, 단점
장점
별도 기술 사용 없이 편하게 빌드, 도커 캐시만 볼륨 마운트하여 사용 가능
ephemeral 기능을 true로 하여 Job이 끝난 Runner는 중지된 뒤 깨끗한 Runner를 재시작하여 사용 가능
단점
1개의 Pod가 1개의 Volume에 마운트할 수 있기 때문에 PVC가 Available 될 때까지 기다려야 함
PVC가 특정 zone에 종속되다보니 pod 또한 affinity로 특정 zone에만 배포될 수 있도록 하거나 node를 특정 zone에 제한해야한다.
참고
이런 단점이 존재하지만 서비스가 별로 없을 경우 가장 간단하게 사용할 수 있다.
gradle을 캐시하는 방법은 build.gradle에서도 정의할 수 있고 efs를 통해서도 캐시데이터를 저장 및 복원할 수도 있다.
docker는 harbor 같은 registry에서도 가능하고 local type으로도 가능하고 s3에서도 가능하다. 대신 S3는 alpha 기능이라 선호되지는 않는다.
'DevOps' 카테고리의 다른 글
[AWS] 클라우드 와치 로그에서 특정 패턴일 경우 Slack 알람 보내는 방법 (0) | 2023.07.02 |
---|---|
[Github Action] Self hosted runner에서 Gradle, Docker image cache #2 - EFS 활용 (8) | 2022.12.19 |
[Istio] EKS에서 Okta와 AWS ALB, Istio를 활용하여 인증, 인가 처리하기 (0) | 2022.11.06 |
[Envoy] Envoy Proxy Architecture (0) | 2022.06.25 |
[Terraform] Atlantis를 통한 Git 협업 #1 - Demo (0) | 2022.05.14 |