Cloud Run - CD 파이프라인 구축

조정훈·2024년 4월 15일

이번에 할 작업

진입 워크플로우를 만들고 빌드해서 GCP의 컨테이너 레지스트리(=Artifact registry) 에 도커 이미지를 푸시한 다음 CloudRun 서비스에 명령어를 보낸다.(새로 변경한 이미지로 바꿔서 올려주게끔) Cloud Load Balancing 이라고 되어있지만, 로드밸런서를 굳이 거치지는 않을거고 외부 인터넷에서 우리의 서비스에 요청을 보낼 수 있게 테스트 해 본다. 현업에서의 작업과 비슷하지만 쉬운 수준으로 만들어본다.

그림 만드는곳) https://draw.io


CD 파이프라인

CD 파이프라인이란 무엇일까요?
CD 파이프라인은 지속적인 배포(Continuous Deployment)의 약자로, 소프트웨어 개발 프로세스에서 코드 변경을 자동으로 배포하는 일련의 단계를 의미합니다.

간단히 말해, 개발자가 코드를 변경하고 저장소에 커밋하면, CD 파이프라인은 자동으로 빌드, 테스트, 배포 과정을 거쳐 새로운 버전의 소프트웨어를 사용자에게 제공합니다.

이 과정은 수동으로 수행하는 것보다 훨씬 빠르고 효율적이며, 인적 오류를 최소화할 수 있다는 장점이 있습니다.


Service Account 생성

GCP 접속 - IAM - 서비스계정 - 서비스계정 만들기


이름과 ID를 만든 후 밑에 '만들고 계속하기' 클릭


권한설정
사진과 같이 권한 검색해서 각각 선택한다.
'Cloud Run 서비스 에이전트'는 2개 나오는데 위에거 선택
선택 후 완료 눌러서 만들기.

내게 필요한 권한을 어떻게 찾을 수 있을까?

github action 에서 에러창을 보면 위와 같은 권한이 필요하다고 나온다.

IAM 좌측에 역할 들어가서 위에 필터에 검색하면 된다.


IAM - 서비스계정 - 방금만든 cloud-run-cd 계정 클릭 - 키 - 새 키 만들기- JSON 으로 만들기 (키는 노출시키면 안된다)


이제 GCP의 Artifact registry로 가보자


새 저장소 만들기 - 위와같이 세팅 후 만들기

우리는 여기에 이미지를 하나하나 업로드 할 예정


Cloud Run은 처음에 이미지를 최소 하나는 업로드 해줘야한다.
이를 위해 Dockerfile 작성하러 가보자

VScode로 가서 Fastapi 작업 할 때 만들어 뒀던 iris-api-server 폴더에 DockerFile 생성

Dockerfile 내용

FROM python:3.11.8-slim

WORKDIR /

RUN pip install poetry

COPY ./poetry.lock ./poetry.lock
COPY ./pyproject.toml ./pyproject.toml

# poetry를 인식 못해서 requirements로 설치
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes

RUN pip install --no-cache-dir --upgrade -r ./requirements.txt

COPY ./app /app
COPY ./entrypoint.sh /entrypoint.sh 
# dockerfile을 실행했을 때 자동으로 실행시켜주는 방법은 2가지가 있다 
# 1. CMD로 정의 - 덮어씌우기가 가능 
# 2. entrypoint - 덮어씌우기 불가능. entrypoint는 도커파일 실행시 무조건 실행되게 하는 명령어

ENTRYPOINT ["bash", "./entrypoint.sh"]

entrypoint.sh 내용

poetry run uvicorn app.main:app --host 0.0.0.0 --port $PORT

poetry인식이 안돼서 requiements로 추출을 했었고,
run uvicorn app.main:app - 서버 실행시키는 명령어
host 0.0.0.0 - 외부의 모든 주소 다 받아주겠다.
--port $PORT - 환경변수의 $PORT 라고 되어있는 포트번호로 쓰겠다는 뜻

기존의 start.sh는 로컬테스트용, entrypoint.sh는 실행용


그 후 터미널에 도커파일 올리는 명령어
docker build -t asia-northeast3-docker.pkg.dev/{프로젝트id}/{서비스이름}/{태그} .
프로젝트id : gcp 위에 My First project 누르면 나옴
서비스이름 : airfitact registry 만들 때 iris-model로 넣었으니 iris-model 넣어준다
태그 : latest
뒤의 . : 현재 위치에서 도커파일을 찾아라

실행하는 명령어
docker run -e PORT=8000 -p 8000:8000 asia-norteast3-docker.pkg.dev/{프로젝트id}/{서비스이름}/{태그}

입력하면 아직 model registry를 안넣었기 때문에 ERROR: Application startup filed Exiting 에러가 나올 것.


환경변수 주입하기
.env 생성 후 내용

전에 start.sh 에서 export 했던 내용 가져오면 된다. 민감한정보는 가렸음

이제 환경변수를 넣어줬으니 명령어를 다르게 쳐야한다.
실행 명령어
docker run --env-file.env -p 8000:8000 asia-northeast3-docker.pkg.dev/{프로젝트id}/{서비스이름}/{태그}

서버가 잘 떴으면 http://localhost:8000/docs 가서 확인해보기

터미널창은 서버 실행중이라 끄려면 Ctrl+C 해야한다.
제대로 안되면 새로운 터미널 창 켜서 docker ps 통해서 docker rm -f {컨테이너id} 로 삭제 한 후 다시 해보자.




Docker Push

이미지를 푸시하기 위해 GCP 권한설정을 해야한다
gcloud auth login
Y 누르면 긴 링크가 나오는데 들어가서 계정에 로그인 한 후 밑에 copy

터미널 창에 떠있는 Enter authorization code: 에 붙여넣기 후 엔터하면 로그인 될 것

그 후 asia-northeast3 리전에 대한 docker설정 진행
gcloud auth configure-docker asia-northeast3-docker.pkg.dev

치면 credHelpers에 asia-northeast3-docker.pkg.dev : gcloud 로 들어갈것임

docker push 명령어
docker push asia-northeast3-docker.pkg.dev/{프로젝트id}/{서비스이름}/{태그}


올라갔는지 확인해보기 (latest라는 태그로 설정한 도커이미지 올라가있음)




Cloud Run

Cloud Run은 Google Cloud Platform(GCP)에서 제공하는 완전 관리형 서버리스 컴퓨팅 플랫폼입니다.
도커이미지를 기반으로 서버를 띄워주는 플랫폼이라고 생각하면 됨.

Cloud Run 접속 후 +서비스만들기 클릭

컨테이너 이미지 URL 선택 눌러서 방금 올렸던 이미지 찾아서 선택

인증되지 않은 호출 허용 : 공개 API로 할것이기 떄문에
CPU가 요청 처리중에만 할당 : 원래 실제 서비스에서는 항상할당됨 으로해야 빨라지겠지만 지금은 테스트이니까 싸게 하는것

아래 컨테이너, 볼륨, 네트워킹, 보안 세팅

컨테이너 포트는 8080으로. 밑에 보면 아까 환경변수에서 $PORT에서 처럼 하는게 좋다고 설명이 나와있음. 컨테이너 명령어, 인수 필요없고

밑에 상태확인 창에서 상태 점검 추가 클릭

추가 해주고 상태점검추가 한번 더 클릭

Startup probe : 처음에 정상적으로 실행 됐는지 확인해준다
Liveness probe : 서비스 중간에 서버가 다운됐는지 확인해준다
초기 지연은 내 서버 상태에따라 바꿔주면 된다.
실패기준 3 : 3번 에러가 나면 죽였다가 다시 살림

아래로 내려보면 버전 자동 확장

최소, 최대 인스턴스 각각 1로 설정
-> 1 이상으로 늘리면 cloud run 에서 알아서 로드밸런싱해서 최대로 늘려준다



변수 및 보안 비밀

아까 추가했던 환경변수들 그대로 추가해준다.(값은 노출x)
완료 누르기.
나중에 프로젝트 때는 값을 직접 넣지말고 보안변수를 따로 만들어서 걔로 넣어주자.


네트워킹

우리의 내부IP에 접속할 수 있도록 아웃바운드 트래픽을 위해 VPC에 연결로 선택해주자.

그 후 서버리스 vpc액세스 커넥터 사용 부분에 네트워크추가를 누르면 default가 없을 텐데 새 VPC커넥터 추가를 눌러서 찾아서 넣어주면된다.(이름은 dafault 아니여도 됨. 알아서하고 기본값으로 설정.)

ip주소범위 10.0.0.0/28
서브넷은10.128.0.0/20 으로 설정.

지금까지 과정을 다 했으면 만들기 클릭

문제없으면 초록색으로 서버 뜬거 확인가능. 이미지URL 통해서 접속할 수 있다. 원래는 현업처럼 하려면 맨 위 그림처럼 공개URL을 쓰지 않고 사설망을 숨겨놓고 gateway같은걸 만들어서 dns설정해서 dns타고 들어와야 라우팅 시켜서 cloud run 하게 만들어야 하는데 실습하는거니까 그냥 공개URL로 사용.

ip주소범위 설정 관련해서 CIDR이란 개념 따로 공부해보자

  • CIDR
    CIDR은 Classless Inter-Domain Routing의 약자로, 도메인 간 클래스 없는 라우팅이라는 뜻입니다.

    쉽게 말해, 인터넷 상의 주소를 관리하는 방식입니다.

    예전에는 클래스별 주소 할당 방식을 사용했는데, 이 방식은 주소 공간을 비효율적으로 사용하는 문제가 있었습니다.

    이를 해결하기 위해 도입된 것이 바로 CIDR입니다.

    CIDR참고링크) https://minha0220.tistory.com/8




Insomnia

https://insomnia.rest/
구글 계정으로 로그인 후 무료버전 다운

프로젝트 만든 후 (Secure Cloud로) 컬렉션 하나 만들어준다.
그 후 환경별로 셋팅을 해보자.(local , test 하나씩)

좌측 Local 의 톱니바퀴 버튼 누르고 Base Environment에 다음과 같이 입력

보라색 박스로 나오게 하려면 {{}}로 묶어주면 된다. scheme, host, base_path 등은변수로 활용해서 cloudrun 환경에서 따로 값을 넣어줄것임.

{
 "base_url": "{{scheme}}://{{host}}{{base_path}}"
 }

Cloud Run이라는 이름으로 환경 만든 후 아래 내용 적용

{
 "scheme": "https",
 "base_path": ""
 "host": "iris-model~~~.app" # cloud run 콘솔페이지에 있던걸로 입력
}

filter 설정
{{_.base_url}} 로 넣어준다

healthcheck

iris predict
body 부분을 json으로 바꾼 후 값을 넣어서 200ok가 뜨는지 확인




Github Action

github 레포지토리 (fatapi-iris쪽) 에서 actions 들어가서
new workflow - cloud run 검색 - Build and Deploy to Cloud Run 선택
하면 기본 템플릿을 제공해준다. 여기서 조금씩 수정해서 사용.
내 레포지토리에서 비교해보자.

.github/workflows/google-cloudrun-docker.yml 경로를 만들어서 yml 파일을 만들어서 넣어준다.(내 레포에서 내용복사해서)(주석 참고)

레포지토리 - settings - Secrets and vairables 에서 각각 필요한 변수들을 넣어준다.
GCP_CREDENTIALS은 복붙해서 넣었으니까 내 레포 참고하자.
나머지는 직접 찾아서 넣어주면됨.

세팅 후 actions 들어가서 오류없이 배포 됐는지 확인

셋팅 자체를 커밋후 풀리퀘 했을 때 돌아가게 만들었기 때문에(개발용 브랜치를 파서 개발 후 풀리퀘해서 main에 merge한 경우를 가정한 것) 이 세팅이 싫다면 나중에 바꿔야함.



추가로 해볼 만한 것

위에서 한 것은 모델 재학습했을 때 재배포가 불가능하다. 왜냐면 도커 이미지에다 모델을 담아서 넣은게 아니고, 모델 뜰 때 프로덕션에 있는거 돌아가게 한거. 그렇다면 만약에 airflow에서 모델을 재학습시켜서 배포한다고 했을 때 또 컨테이너를 만들어서 올릴것이냐? 하면 소스코드도 안바뀌었는데 그럴필요가 없다.
-> 모델 배포를 api call 로 만들어서 post endpoint 하나 만들어서 요청을 받으면 mlflow에 있는 모델을 다시 불러오게끔 만들어보자. 그 다음에 airflow 맨 마지막 task에 current version의 모델이 있고, 프로덕션 버전이 있을것인데 두 버전이 만약 다르다? = 새로운 버전이다. 이거를 xcom으로 pull 땡겨서 검사를해서 만약 값이 다르다면 서비스의 endpoint에 요청을 때리게끔 만들어보자. 그러면 새로 배포됐을때 api쪽에서는 요청을 받아서 mlflow쪽에서 모델을 재 리로딩 해서 배포하는 식으로.
사실 현업에서는 build tool 을 따로 써서(aws같은) airflow쪽에서 처리하도록 만들지만, 그렇게하면 너무 복잡하므로 간단하게 구현해보았다.
(http post 요청에 x-api-key키라는 헤더를 포함해서 보내는 방법이 있다 찾아보자)
x-api-key는 적당히 난수 만들어서 넣자. 얘도 환경변수로 넣어서 해보자.

router쪽에서 endpoint 만들어보자
import Header 해서 Header(..., alias='x-api-key') 이런식으로 하면 헤더를 읽을수 있다. 좀 더 찾아서 해보면 될거같다~

0개의 댓글