k8s 클러스터 내에 codecov 호스팅하기
helm chart를 사용하여 codecov k8s에 호스팅하는 과정안녕하세요. 페이히어 백엔드 손윤석입니다.
지난 1월 저희는 유료플랜을 통해 사용하던 codecov를 제거하고 self-hosted 형태로 k8s 클러스터에 codecov를 구성하였습니다. 본 글에서는 codecov를 구성하게 된 배경과 과정을 자세히 설명합니다.
배경
비용
codecov에서 제공하는 Pro plan을 사용하게 되면 인당 10$/월 단위로 지불을 해야합니다. 인원이 늘어날수록 그 비용은 더 늘어나고 장기적으로 봤을 때 부담이 되는 금액입니다.
페이히어 백엔드는 인원이 20명이 넘어 매달 200달러 이상을 codecov에 내고 있었습니다.
자주 발생하는 장애
거의 2~3달에 한 번씩은 sentry.io 장애가 발생하여 백엔드 동료분들의 업무에 블로킹이 되었던 적이 꽤나 자주 있었습니다. 페이히어 백엔드에서는 테스트 코드의 중요도를 높게 보고 있어 CI 통과 조건에 codecov coverage를 필수조건으로 설정하고 있는데 장애가 발생하면 coverage 업로드가 아예 동작하지 않아 굉장히 불편했습니다.
아래는 codecov backend의 상태 리포트입니다. 가장 심각했던 날 (23년 4월 6일) 에는 주요 기능이 2시간 5분가량 outage 상태였다고 합니다. ^^
과정
helm 차트 분석
구성
codecov
codecov_config: |
setup:
enterprise_license: "{라이센스 키}"
admins:
- service: github
username: "LeoQuote"
http:
cookie_secret: "some-random-string"
timeseries:
enabled: false
# global coverage.yml config
site:
comment:
# default layout is wierd with duplicated reach
layout: "reach,diff,flags,tree"
github:
client_id: "clientidxxxx"
client_secret: "secretxxx"
# global_upload_token: "<upload-token>"
webhook_secret: "verysecret"
integration:
id: 22
pem: /config/pem/file/{pem키 이름}.pem
실질적으로 codecov 세팅을 위한 설정 yaml이 작성되는 부분입니다.
- setup
- enterprise_license: 밑에서 설명할 라이센스 key를 넣는 부분입니다.
- admins: 리스트 형태로 관리자가 될 계정에 대한 정보를 넣는 부분입니다.
- http
- cookie_secret: base64 랜덤값을 넣었습니다.
- github
- client_id: 밑에서 설명할 codecov github app에 대한 Client ID 입니다.
- client_secret: github app에 대한 Client Secret 값 입니다.
- webhook_secret: github app에 설정하는 webhook에 대한 secret 값 입니다. 사용자가 임의로 설정합니다.
- integration:
- id: github app에 대한 App ID 입니다.
- pem: pem키 디렉토리 위치 정보
volumes:
- name: {볼륨 이름}
secret:
secretName: {시크릿 이름}
volumeMounts:
- name: {볼륨 이름}
mountPath: "/config/pem/file" # 예시
readOnly: true
우선 Github app에서 발급받은 pem key를 컨테이너에 포함시켜주어야 합니다. 매 배포마다 커널로 진입해서 pem key를 넣어줄 수는 없으니 secret 리소스를 직접 생성하고 파드 내 볼륨 리소스를 생성하여 연결 시켜주는 작업을 합니다.
apiVersion: v1
kind: Secret
metadata:
name: {시크릿 이름}
namespace: {네임스페이스 이름}
data:
{pem키 이름}.pem: .pem 파일 -> base64 hash값
type: Opaque
openssl base64 -in {파일명}.pem # base64 해시 값 인코딩 명령어
Postgresql
codecov는 postgresql을 지원합니다. helm을 통해 embedded 형태로 띄울 수 있지만 페이히어 백엔드에서는 이미 사용중인 aws rds postgresql이 있기 때문에 해당 db와 연결했습니다.
extraEnvs:
...
- name: SERVICES__DATABASE_URL
value: postgresql://{id}:{password}@{호스트 주소}:5432/codecov
...
minio
현재 codecov helm에서 embedded minio는 미구현되어 있어 external minio를 따로 띄워야 합니다.
ingress:
enabled: true
apiVersion: networking.k8s.io/v1
ingressClassName: alb
hostname: minio.payhere.dev # 예시
path: /*
pathType: ImplementationSpecific
servicePort: 9001
apiIngress:
enabled: true
apiVersion: networking.k8s.io/v1
ingressClassName: alb
hostname: minio-api.payhere.dev # 예시
path: /*
pathType: ImplementationSpecific
servicePort: 9000
ingress 설정과 apiIngress 설정이 있는데 ingress는 사용자가 웹으로 관리하기 위한 페이지이고 codecov가 실질적으로 데이터를 저장하기 위해 통신하는 api 서버는 apiIngress 입니다.
minio:
embedded: false
externalHost: minio-api.payhere.dev. // apiIngress로 생성된 호스트 주소를 넣으면 됩니다.
externalPort: 9000
# externalMinio: // 해당 옵션으로 minio가 아닌 클라우드 서비스의 저장소를 이용할 수 있다.
추가적으로 minio와 연결하기 위한 환경변수 설정을 해주어야 합니다. minio가 정상적으로 띄워졌다면 아래와 같이 Access Key와 버킷을 생성할 수 있습니다.
이후 codecov values.yaml에 환경변수를 선언해주어야 합니다. helm 차트만으로 지원하는 기능이 한정적이기 때문에 이를 고려하여 환경변수를 통해 config을 변경할 수 있도록 codecov에서 설계가 되어 있습니다. 설정 매핑 방식에 대한 자세한 코드는 해당 링크 에서 찾아보실 수 있습니다.
extraEnvs:
...
- name: SERVICES__CHOSEN_STORAGE
value: minio
- name: SERVICES__MINIO__ACCESS_KEY_ID
value: # minio access key
- name: SERVICES__MINIO__SECRET_ACCESS_KEY
value: # minio secret key
- name: SERVICES__MINIO__REGION
value: ap-northeast-2
- name: SERVICES__MINIO__BUCKET
value: # minio 버킷 명
- name: SERVICES__MINIO__HOST
value: # minio api 호스트 주소
...
라이센스
앞서 말씀드린 라이센스 키 관련 필요한 작업입니다. 우선 codecov/self-hosted 레포지토리를 pull 받아야 합니다. (로컬에 pull 받아도 상관없음)
python3 scripts/license.py new --expires={만료일} --company={회사명} --users={유저수}
위 라이센스 키 생성 명령어를 콘솔에 입력하면 아래와 같은 결과가 출력됩니다
al4VtCZsPBG+SZJis+iEY/bljTc0bzJcETdDT/bxXo0jSCGh50IeByPy4QX8gd16+ebOgJUh03EQ+cxtd+aBjEH7a+1f+dyffg5TPXjEEF+/Eewil2AhJIa9zKuuTgVGjCoo+BbmB9v8gJvvK+bBYWsWR+j2BJxCkQ4PacUaWairdMDf20XSs4qLfGc/BLb5vbuZAVPiXu8Q==
# ^ 출력되는 키를 위에서 언급한 enterprse_license 필드에 입력하면 됩니다.
License expires on 9999-12-31
---- Internal purposes only ----
{'company': '{회사명}', 'expires': '{만료일}', 'url': None, 'trial': False, 'users': {유저수}, 'repos': None, 'pr_billing': False}
codecov_config: |
setup:
enterprise_license: # 여기입니다
Github 연동
helm을 통한 codecov + minio 리소스 생성이 끝나면 Github App을 통해 연동을 합니다.
{호스트주소}
는 codecov self-hosted 주소입니다.
ex) codecov.payhere.dev
OAuth Callback URL
Webhook
webhook secret은 반드시 설정해주어야 합니다.
Actions 설정
이후 github actions yaml에 설정 필드를 하나 추가해주면 모든 과정이 끝이 납니다.
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
flags: payhere
**url: https://codecov.payhere.dev # 호스트 주소**
verbose: true
완성작
이번 계기로 codecov self-hosted를 만들어서 기존에 사용하던 codecov를 제거하고 비용을 절감할 수 있었습니다. 또한 기존에 백엔드에서만 사용하던 codecov를 이제는 다른 개발직군에서도 Github App을 통해서 쉽게 연동해서 코드 커버리지를 측정하고 PR 내에서 Github Actions을 통해서 커버리지 통과여부를 확인 할 수 있게 되었습니다.
끝 마치며
사실 이번에 연동을 하며 훈련소도 다녀왔고 1달 조금 넘는 시간이 걸렸던 것 같은데 가장 큰 원인은 codecov values.yaml
에 오타가 있어 Github와 연동이 제대로 되지 않았던 부분이 큽니다.
원인을 찾으려고 codecov 관련된 레포지토리들을 일주일정도 파본 것 같은데, 원인은 오타 한 줄이라는 게 굉장히 충격적이었습니다.
이번 계기 덕분에 개인적으로 오픈소스 기여도 할 수 있었습니다.
이상 긴 글 읽어주셔서 감사합니다!