DevOps & MLOps

2023 오픈소스 컨트리뷰션 DevOps & MLOps (3) DevOps편 (ACI, AKS 두 개 소 동시 배포 자동화)

jamie-lee 2023. 8. 6. 00:11

전 편에서 ACI에 배포 자동화를 구축했다. 이제 더 나아가서 ACI, AKS 두 서비스에 동시에 배포할 수 있도록 구성할 것이다! ACI 배포 구성은 마쳤으므로, AKS 배포 자동화를 알아본다.

ACI, AKS 두 타겟에 deploy하기

[!NOTE] AKS란
cluster를 빠르게 배포하고 관리할 수 있는 관리형 쿠버네티스 서비스.

CLI로 AKS 클러스터 만들기

여기를 참고한다 ☞ Quickstart: Deploy an Azure Kubernetes Service (AKS) cluster using Azure CLI - Azure Kubernetes Service | Microsoft Learn

아래 명령어를 통해 AKS 클러스터를 생성한다. 나는 OsscaDevops-Cluster라는 클러스터를 생성했다.

az aks create -g <리소스-그룹-이름> -n <클러스터-이름> \
	--enable-managed-identity \
	--node-count 1 \
	--generate-ssh-keys \

"Registration succeeded."라는 문구와 함께 JSON 포맷의 response를 받으면 성공이다.
리소스 그룹에 Kubernetes 서비스라는 형식으로 잘 생성된 것을 확인.

Pasted image 20230802205111.png

❗ISSUE --enable-addons monitoring 인자 에러

공식 도큐먼트에 기재된 명령어는 아래와 같은데,
--enable-addons monitoring 인자는 Azure Monitor Container insights를 가능하게 하는 옵션이라고 한다.

az aks create -g <리소스-그룹-이름> -n <클러스터-이름> \
	--enable-managed-identity \
	--node-count 1 \
	--enable-addons monitoring \
	--generate-ssh-keys \

문제는 저걸 넣어서 실행시켰더니 아래와 같은 에러가 뜬다.

Conflict({"ISSUE":{"code":"MissingSubscriptionRegistration","message":"The subscription is not registered to use namespace 'microsoft.insights'. See https://aka.ms/rps-not-found for how to register subscriptions.","details":[{"code":"MissingSubscriptionRegistration","target":"microsoft.insights","message":"The subscription is not registered to use namespace 'microsoft.insights'. See https://aka.ms/rps-not-found for how to register subscriptions."}]}})

subscription 얘기가 있는 걸로 보아 구독 관련 이슈인 것 같다.
찾아보니 "microsoft.insights"라는 기능을 등록하지 않아서 생기는 문제라고 한다. ‘microsoft.insights’ 네임스페이스에 대한 등록이 필요하다.

Azure에서는 사용자가 특정 서비스를 사용하기 전에 해당 서비스에 대해 구독을 등록해야 한다.
이는 사용자가 원치 않는 서비스에 대한 비용이 청구되는 것을 방지하며, Azure에서 해당 서비스의 사용량을 추적할 수 있게 한다.

Azure 포털 > 구독 > 사용하고 있는 구독 선택 > 리소스 공급자에 접속한다.
검색 창에 'microsoft.insights’를 입력하고, 해당 제공자를 찾은 후 클릭하여 '등록’을 누른다.

우선 나는 이 기능을 등록하지 않고 진행했다.

Kubernetes 명세 파일 작성하기

여기를 참고한다 ☞ Build, test, and deploy containers to Azure Kubernetes Service using GitHub Actions - Azure Kubernetes Service | Microsoft Learn
https://learn.microsoft.com/en-us/azure/aks/tutorial-kubernetes-prepare-app
ACI 뿐만 아니라 이번에는 컨테이너를 활용하는 다른 azure 서비스에도 배포하는 작업을 진행한다.
결과적으로 동시에 두 개소에 자동 배포하는 것이 목표이다.
Azure 도큐멘테이션의 예제 repo (Azure-Samples/azure-voting-app-redis: Azure voting app used in docs. (github.com))의 yaml 파일을 참고하여 아래처럼 수정해보았다. (※ 이후 에러를 해결하면서 일부 수정함)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ossca-devops-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ossca-devops-app
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  minReadySeconds: 5 
  template:
    metadata:
      labels:
        app: ossca-devops-app
    spec:
      nodeSelector:
        "kubernetes.io/os": linux
      containers:
      - name: ossca-devops-app
        image: osscadevopsimages.azurecr.io/ossca-devops-app:latest
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 250m
          limits:
            cpu: 500m
---
apiVersion: v1
kind: Service
metadata:
  name: ossca-devops-app
spec:
  type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: ossca-devops-app

Github actions file 수정하기

기존에 작성한 main_deploy.yml 파일을 수정하여 AKS 배포 부분도 작성해야 한다.
우선 기존의 deploy 작업을 공식 도큐먼트를 참고하여 아래와 같이 수정하였다.

    deploy:
        needs: build
        runs-on: ubuntu-latest
        steps:
        - name: Set AKS context
          id: set-context
          uses: azure/aks-set-context@v3
          with:
            resource-group: ${{ secrets.RESOURCE_GROUP }}
            cluster-name: ${{ secrets.CLUSTER_NAME }}

        - name: Setup kubectl
          id: install-kubectl
          uses: azure/setup-kubectl@v3

        - name: Deploy to AKS
          id: deploy-aks
          uses: Azure/k8s-deploy@v4
          with:
            namespace: 'default'
            manifests: |
              ossca-devops.yaml // 쿠버네티스 yml 파일명 
            images: ${{ secrets.REGISTRY_LOGIN_SERVER }}/ossca-devops-app:${{ github.sha }}
            pull-images: false 

이 때 CLUSTER_NAME이라는 secret이 사용되므로 깃헙 시크릿을 생성한다.

공식 문서에 따르면, AKS에 배포하기 위해 아래의 총 6단계를 거친다.

  1. Checkout source code uses the GitHub Actions Checkout Action to clone the repository.
  2. ACR build uses the Azure Container Registry Build Action to build the image and upload it to your registry.
  3. Azure login uses the Azure Login Action to sign in to your Azure account.
  4. Set AKS context uses the Azure AKS Set Context Action to set the context for your AKS cluster.
  5. Setup kubectl uses the Azure AKS Setup Kubectl Action to install kubectl on your runner.
  6. Deploy to AKS uses the Azure Kubernetes Deploy Action to deploy the application to your Kubernetes cluster.

기존 yaml 파일에선 ACI에 배포하기 위해 ACR 빌드하기 전 azure login 단계를 수행한다.
따라서 이미 로그인이 되어 있다고 보고 나는 4~6단계만 포함시켜 주었다.
이제 이 변경사항을 저장소에 반영하여 잘 돌아가는지 (어떤 에러가 날런지 ㅎ) 확인해보자.

❗ISSUE “please run ‘az login’ to setup account.”

Pasted image 20230802212310.png

아무래도 내 생각이 틀렸던 듯 싶다.
위에서 말한 6단계 중, 3단계를 또 작성해야 하는 것 같다.
build 작업과 deploy 작업은 각기 다른 runner에서 실행된다(ubuntu-latest). 새로운 가상환경에서 실행되므로 각기 로그인을 해주어야 하는 것이 맞는 거 같다.

아니 그런데, 이번에는 github action이 "Waiting for a runner to pick up this job…"이라는 말만 내놓고 아무런 작업이 일어나지 않았다.
스택 오버 플로우(ISSUE “Waiting for a runner to pick up this job” using GitHub Actions - Stack Overflow)에서 운영체제를 제대로 명시했는지를 확인하라고 했고, 이럴수가 …

“runs-on: ubuntu-latests” 라고 작성한 나의 오타를 발견하였다. 😅

고치고 다시 PR을 올리니 해당 단계를 통과한다.

❗ISSUE “no such file or directory, lstat ‘ossca-devops.yaml’”

Pasted image 20230802214855.png

분명 파일이 존재하는데 또 뭐가 문제일까…
디렉토리를 제대로 찾아가지 못하고 있나 의심이 들었다.

혹시 몰라 <프로젝트-폴더명>/ossca-devops.yaml 로 수정하여 다시 확인였으나 동일한 오류를 내뿜었다.
루트 디렉토리가 어떻게 잡히고 있는 건지 궁금했다.
GPT4가 아래 스텝을 이용해 디렉토리와 파일을 확인해보라는 꿀팁을 주었다.

- name: Check current directory
  run: pwd

- name: List files in current directory
  run: ls

그래서 deploy 단계에서 해당 스텝을 포함하여 실행해본 바, 루트 디렉토리를 찾아가는 것은 문제가 없는데
아래처럼 ls -a를 실행하니 아무런 프로젝트 파일이 나타나지 않았다.

Pasted image 20230802225301.png

꽤 여러 번의 삽질 끝에 원인을 찾아냈다.
저장소 코드를 가상환경으로 복사하는 작업은 uses: actions/checkout@main을 통해서 일어난다.
즉, 앞서 말한 6단계 중 1단계를 의미한다.

AKS 배포 작업 yaml을 작성하면서, build 작업에서 checkout@main을 진행하니까 한 번 더 할 필요가 없다고 생각했다. ⇒ 새로운 가상환경에서 deploy 작업이 일어나는 것을 간과한 것이다.
더군다나 ACI에 배포하는 작업을 진행할 때는 checkout@main을 진행할 필요가 없었기 때문에 더더욱 간과했다. ⇒ 이 경우는 저장소의 코드를 건드릴 필요 없이 Azure에 저장소에 올라가있는 이미지를 실행만 하면 되는 작업이었기 때문이다.

하여튼 결론적으로 AKS 배포 작업을 하면서 새로운 가상환경을 실행시켰고(build 작업을 진행한 runner가 아닌)
context를 설정하는 작업해서 저장소의 manifest 파일을 이용해야 하기 때문에,
저장소의 코드를 deploy 작업이 일어나는 가상환경으로 복사해야 한다.
그래서 checkout@main이 수행되어야 한다.

최종적으로 위의 6단계 中 2단계를 제외한 나머지를 AKS deploy에서 수행해주었다.

Pasted image 20230802232723.png

그 결과 프로젝트 파일이 위처럼 잘 복사되었고, manifest 파일도 잘 찾아서 set AKS context 단계를 마무리한 것을 확인했다.

❗ISSUE ‘ossca-devops-app’ 이미지를 찾지 못하는 에러

Pasted image 20230802232833.png

잘 넘어가나 했는데 아무래도 쿠버네티스 manifest 파일에서 잘못 설정한 부분이 있는 모양이다. (작성하면서 조금 의심스럽긴 했다. )
Azure 포털에서도 아래와 같은 결과를 받았다.

Pasted image 20230802232942.png

ImagePullBackOff는 쿠버네티스가 도커 이미지를 저장소에서 가져오는데 실패했음을 의미한다.
짐작컨대 이미지 명에서 뭔가 에러가 있었던 것이 틀림없다.
ACR로 달려가서 실행해야 하는 이미지 명을 살펴본다.

Pasted image 20230802233251.png

이렇게 생겼다.
manifest 파일에서 ossca-devops-app이라고 작성했던 부분을 flaskapp으로 바꿔준다.

그런데 ….

Pasted image 20230802234931.png

위처럼 flaskapp:해쉬값이 이미지 저장소의 가장 최신 이미지와 일치하는 것을 확인했음에도 동일한 오류가 뜨고 있다.

Azure 포털에서 해당 pod의 이벤트 로그를 살펴보았더니, 에러 메시지 응답에서 아래와 같은 note를 발견할 수 있었다.

  Failed to pull image
  "osscadevopsimages.azurecr.io/flaskapp:6aae6e91765255fc3142c517dea8c65ebfe3f0c7":
  rpc ISSUE: code = Unknown desc = failed to pull and unpack image
  "osscadevopsimages.azurecr.io/flaskapp:6aae6e91765255fc3142c517dea8c65ebfe3f0c7":
  failed to resolve reference
  "osscadevopsimages.azurecr.io/flaskapp:6aae6e91765255fc3142c517dea8c65ebfe3f0c7":
  failed to authorize: failed to fetch anonymous token: unexpected status from
  GET request to
  https://osscadevopsimages.azurecr.io/oauth2/token?scope=repository%3Aflaskapp%3Apull&service=osscadevopsimages.azurecr.io:
  401 Unauthorized

마지막에 401 Unauthorized가 눈에 띈다.
AKS가 ACR에 접근하기 위한 자격증명이 문제인 것일까?

아래 다음 명령어를 통해 AKS의 클러스터가 ACR에 접근해 이미지를 받아올 수 있는 권한을 부여하자.

az aks update -n <클러스터-이름> -g <리소스-그룹명> \
	--attach-acr <ACR-name>

우선 동일한 에러는 나지 않는 것을 확인했다.

❗ISSUE AKS CrashLoopBackOff

그러나 이번에는 또 다른 에러!

Pasted image 20230803001639.png

CrashLoopBackOff 상태는 쿠버네티스에서 파드가 반복적으로 실패하고 재시작을 시도하는 상황을 의미한다고 한다.
파드의 컨테이너가 시작되지 않거나 시작 후 바로 종료되는 경우라는데, 일반적으로 애플리케이션 코드의 문제, 컨테이너 이미지의 문제, 잘못된 컨테이너 구성, 부적절한 리소스 할당 등 다양한 원인으로 인해 발생할 수 있다고 한다.

지금 내 코드는 단순 문자열 출력 후 종료되는 프로그램이라서 그런 걸까?
이 점도 그렇고, 일단 서버를 띄워서 외부 IP로 접속이 되는지 확인할 필요도 있었기 때문에 제대로 된 flask 보일러 플레이트 코드를 다음과 같이 작성하여 app.py에 업데이트 했다.

# Import the Flask module that has been installed.
from flask import Flask

# Creating a new "app" by using the Flask constructor. Passes __name__ as a parameter.
app = Flask(__name__)

# Annotation that allows the function to be hit when the specific HTTP request is performed.


@app.route('/')
def home():
    return 'index page'


@app.route('/hello')
def hello():
    return {"msg": "hello, ossca"}


# Main driver function
if __name__ == '__main__':
    # run() method of Flask class runs the application
    # on the local development server.
    # app.debug = True
    app.run(host='0.0.0.0', port='5000') # host를 0.0.0.0으로 지정해줘야 외부 접근 가능

이제 github action 상의 deploy 오류는 해결되었다! 짝짝짝.
하지만 또 동일한 CrashLoopBackOff 쿠버네티스 오류가 발생했다.
왜 컨테이너가 종료되는지 컨테이너 터미널 로그를 확인하고 싶었다. Azure cli로 pod 로그를 확인하는 방법을 아래와 같이 찾았다.

  1. 먼저 az aks get-credentials 명령을 사용하여 클러스터에 연결한다. 이 명령은 ~/.kube/config 파일에 쿠버네티스 클러스터에 대한 접속 정보를 저장한다.
az aks get-credentials --resource-group <your-resource-group> --name <your-cluster-name>
  1. kubectl 명령을 사용하기 위해 설치부터 하고,
sudo az aks install-cli
  1. 문제가 일어난 파드의 이름과 네임스페이스를 입력하고 로그를 확인해본다.
kubectl logs <pod-name> -n <namespace>

아뿔싸. 모듈 설치를 안 해주었구나 … ^^
Pasted image 20230804181729.png

일단 임시방편으로 도커 파일에 flask 모듈을 설치하는 명령어를 작성했다.

Pasted image 20230804182708.png

"Running"이라는 반가운 초록불이 떴다. 🙌

❗ISSUE: 외부 접속 불가

참고 ☞ Service | Kubernetes

자, 이제 방금 쿠버네티스로 띄운 flask 앱에 접속해 hello ossca를 잘 보여주는지 확인할 차례만 남았다.
네트워크와 관련된 부분은 쿠버네티스의 "Service"라는 기능에서 찾아보면 된다. (쿠버네티스 스터디에서 배움✌️)
대시보드에 접속하면 여러 가지 네트워크 정보들이 보인다.
느낌상 외부 IP에 접속하면 되지 않을까? 해서 클릭했지만, 접속되지 않는다.

또 뭔가가 잘못되었다!

제일 먼저 의심이 가는 것은 포트 문제이다.
flask 앱은 디폴트로 5000번 포트로 지정되어 있다. 반면 쿠버네티스 manifest에는 80번 포트를 쓰겠다고 명시되어 있다.
사실 문제가 될 것 같다는 의심을 했지만 에러 났을 때 확실히 알아보려고 일단 진행했었다.

요 문제를 한 번 파헤쳐보자.

우선 기존의 쿠버네티스 manifest 파일에서 포트 부분을 살펴보면, 아래 두 군데에서 언급하고 있다.

...
kind: Deployment
...
	spec:
      nodeSelector:
        "kubernetes.io/os": linux
      containers:
      - name: flaskapp
        image: osscadevopsimages.azurecr.io/flaskapp:latest
        ports:
        - containerPort: 80

...

---
apiVersion: v1
kind: Service

...

spec:
  type: LoadBalancer
  ports:
  - port: 80

...

이 부분을 건드리면 될 것 같다는 느낌이 온다.
그런데 서비스의 세부 정보를 살펴보면 아래와 같은 항목들을 볼 수 있다.

형식: LoadBalancer 
클러스터 IP: 10.0.155.51 
외부 IP: 20.196.242.36
nodeport: 32055 
port: 80 
protocol: TCP 
대상 포트: 80 
endpoint: 10.244.0.21:80

“노드 포트”, “포트”, “대상 포트” 이 세 가지 포트를 언급하고 있는 것을 알 수 있다.
난 도대체 어떤 포트를 건드려야 하는 걸까? 궁금해졌다.
특히 "포트"와 "대상 포트"의 차이가 무엇인지 모르겠다.
한번 개념을 정리하고 갈 필요가 있을 것 같아서 알아보았다.

  1. “형식(Service Type): LoadBalancer”:
    • 이는 서비스의 유형을 나타낸다
    • LoadBalancer 유형의 서비스는 쿠버네티스 클러스터 외부에서 접근 가능한 공개 IP 주소를 할당받는다
    • 클라우드 제공 업체(내 경우 Azure)의 로드 밸런서를 사용하여 서비스에 대한 트래픽을 적절한 파드로 분산시킨다
  2. “클러스터 IP: 10.0.155.51”:
    • 이는 쿠버네티스 클러스터 내부에서만 접근 가능한 IP 주소
    • 마치 사무실 내부 전화 시스템과 같은 것으로, 사무실 내에서는 특정 번호(예: 내선 번호)만으로 서로 통화를 할 수 있지만, 이는 사무실 바깥에서는 접근할 수 없는 방식이다.
    • 예를 들어, 웹 애플리케이션과 데이터베이스가 동일한 클러스터 안에 있고 웹 애플리케이션이 데이터베이스에 연결해야하는 경우에 Cluster IP를 사용하게 된다. 웹 애플리케이션은 데이터베이스 서비스의 Cluster IP와 포트를 통해 데이터베이스에 접속하게 된다.
  3. “외부 IP: 20.196.242.36”:
    • 이는 클러스터 외부에서 서비스에 접근할 수 있도록 하는 IP 주소
    • 이는 공용 전화번호와 같은 것. 누구나 이 번호를 통해 특정 사람이나 부서에 전화를 걸 수 있다.
    • 예를 들어, Kubernetes 클러스터에 배포된 웹 사이트에 사용자가 접속하려면 외부 IP를 통해 접속하게 된다. 이 때 사용자는 웹 브라우저에서 외부 IP 주소(또는 해당 IP와 맵핑된 도메인 이름)를 입력하게 된다.
  4. “NodePort: 32055”:
    • 클러스터의 모든 노드에서 동일한 포트로 서비스를 노출한다
    • 외부에서 직접 노드에 접속해야 할 때 사용한다. 예를 들어, 특정 보안 규정 때문에 로드밸런서나 Ingress를 사용하지 않고, 직접 노드에 접속해야 하는 경우. 이 때 사용자는 http://<노드의 IP>:<노드포트>를 통해 서비스에 접속하게 된다. 이러한 경우가 일반적이지는 않다고 함.
  5. “port: 80”:
    • 서비스가 자신에게 오는 트래픽을 받아들이는 포트. Service Port.
    • 클러스터 내부나 외부에서 특정 서비스에 접근하기 위해 사용된다. 예를 들어, 웹 애플리케이션이 80 포트를 사용하도록 설정되어 있다면, 사용자는 웹 브라우저에서 http://<외부 IP>:80를 통해 웹 애플리케이션에 접속하게 된다.
  6. protocol: TCP: 서비스가 사용하는 프로토콜로, TCP가 디폴트 프로토콜이다. (☞ 지원하는 다른 프로토콜 종류 Protocols for Services | Kubernetes)
  7. 대상 포트(Target Port): 80:
    • 이는 서비스가 자신에게 온 트래픽을 전달할 파드의 포트(Pod’s port) 로, 어떤 Pod의 어떤 포트로 요청을 전달해야 하는지를 결정하는데 사용한다.
    • 예를 들어, 웹 애플리케이션을 실행하는 Pod가 5000 포트에서 동작하도록 설정되어 있다면, 서비스의 대상 포트는 5000으로 설정된다.
  8. endpoint: 10.244.0.21:80:
    • 엔드포인트는 서비스가 트래픽을 전달할 대상
    • 여기서는 IP 주소가 10.244.0.21이고 포트가 80인 파드를 대상으로 한다
    • 여기에는 서비스의 선택자에 매치되는 모든 파드의 IP 주소와 포트가 포함될 수 있다

위 사항 중, 내가 쿠버네티스 manifest로 변경하거나 지정할 수 있는 사항은

  1. Service Type: LoadBalancer, NodePort, ClusterIP 등 서비스의 타입
  2. Port (Service Port): 서비스가 외부로 노출하는 포트
  3. Target Port (Pod’s port): 서비스가 요청을 전달할 Pod의 포트
  4. NodePort: 서비스 타입이 NodePort로 설정된 경우, 특정 노드 포트를 직접 지정할 수 있다. 하지만 대부분의 경우 Kubernetes가 자동으로 노드포트를 할당한다. (default: 30000-32767)

다음의 내용은 직접 지정하거나 변경할 수 없다:

  1. Cluster IP: 서비스를 생성하면 Kubernetes 시스템이 자동으로 할당
  2. External IP: 로드밸런서 또는 Ingress 컨트롤러를 사용하는 경우 클라우드 제공업체(AWS, Google Cloud, Azure 등)가 자동으로 할당

그럼 여기까지 공부하고 다시 아래의 서비스 설정을 살펴본다면,

형식: LoadBalancer 
클러스터 IP: 10.0.155.51 
외부 IP: 20.196.242.36
nodeport: 32055 
port: 80 
protocol: TCP 
대상 포트: 80 
endpoint: 10.244.0.21:80

여기서 "대상 포트"가 80번이 되면 안되고, flask app이 사용하는 5000번 포트로 지정되어야 제대로 작동하겠구나라는 걸 알 수 있다.
port(서비스 포트)는 건드릴 필요가 없을 것이다. “외부IP:서비스포트” 로 접속한 클라이언트의 요청을 Service가 5000번 포트를 사용하는 flask 앱 컨테이너로 보내주기만 하면 되기 때문.

자, 그러면 대상 포트를 5000으로 맞춰주기 위해서는 어떻게 해야 할까?
아까 manifest 파일에 port 말고도 containerPort 라는 필드도 존재했다. 여기에 더불어 kind: Serviceports 필드에 targetPort 필드를 추가하여 대상 포트를 지정할 수 있다. 바로 아래처럼.

...
kind: Service
...
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 5000 # 여기 
...

containerPorttargetPort 두 가지 필드에 대해 정리하면 아래와 같다.

  • containerPort : 선택사항. 컨테이너가 이 포트에서 수신을 대기하고 있다고 명시적으로 표현하는 것. 작성하지 않으면 쿠버네티스는 해당 컨테이너가 몇 번 포트에서 수신 대기 하고 있는지 알 수 없다.
  • targetPort : 선택사항. 작성하지 않으면 자동으로 port의 값을 참조한다고 함. (Service | Kubernetes)

결론적으로 둘 중 하나는 꼭 제대로 작성해야 한다는 말이 된다~!

쿠버네티스 공식문서 (Service | Kubernetes)를 참고하여 아래처럼 포트 설정을 완료했다.

...
kind: Deployment
...
	spec:
      nodeSelector:
        "kubernetes.io/os": linux
      containers:
      - name: flaskapp
        image: osscadevopsimages.azurecr.io/flaskapp:latest
        ports:
        - containerPort: 5000 # flask 앱의 디폴트 포트로 변경 (추후 ACI 이슈 때문에 80으로 변경)
          name: flask-app     # name을 지정하여 변수처럼 사용 

...
---
apiVersion: v1
kind: Service
...
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: flask-app   # flask-app으로 대상 포트 지정
...

대시보드에서 확인하니 아래와 같이 대상 포트가 변경된 것을 확인할 수 있고,

Pasted image 20230804211629.png

외부 IP로 접속했을 때 성공 메시지가 뜨는 것을 확인했다. 만세~! 🙌🙌

Pasted image 20230804200147.png

AKS, ACI 두 개 소 배포할 때 조심해야 할 점!! (ACI 포트 매핑)

이제 ACI, AKS 두개소에 동시 배포 하기 위해 deploy를 위한 github actions yml 파일에서 ACI 배포하는 부분을 주석 해제한다.
여기서 조심해야 할 점

  1. job 네이밍이 중복되면 안 된다는 점이다.(이 부분 때문에 에러를 겪었다.) deploy-to-ACI, deploy-to-AKS 이런 식으로 네이밍을 해줬다.
  2. ACI도 포트 설정을 해줘야 한다. (디폴트로는 80으로 지정되어 있다고 한다. 공식문서 참고.)
    문제는 github action workflow 파일에서 지정하는 방법을 찾아보려고 했는데, 찾지 못했다.
    그래서 도커파일에 expose 5000 명령어를 추가했으나… 여전히 디폴트 포트인 80으로 열린다. 🤔
    공식문서(Troubleshoot common issues - Azure Container Instances | Microsoft Learn)의 트러블슈팅을 참고하니, "Azure Container Instances doesn’t yet support port mapping like with regular docker configuration."라는 문구가 있었다. 일반적인 도커 설정을 통한 포트 매핑을 지원하지 않는다고 한다. (ACI는 간단한 테스트 목적으로 빠르게 앱을 띄우는 것이 목적인 서비스라서 그런 것 같다.)
    그래서 아래처럼 Azure CLI를 통해 환경 변수를 넘겨줌으로서 포트 매핑을 할 수 있다고 전했는데, 문제는 그렇게 되면 배포 시마다 해당 명령어를 입력해줘야 하므로 자동 배포가 아니게 된다.
az container create --resource-group myResourceGroup \
--name mycontainer --image mcr.microsoft.com/azuredocs/aci-helloworld \
--ip-address Public --ports 9000 \
--environment-variables 'PORT'='9000'

그래서 제일 빠른 방법이자 유일한 방법은 flask 앱의 디폴트 포트를 80으로 바꿔주는 방법이었다! (진작에 이렇게 했다면 사실 AKS 포트 지정 삽질을 할 필요가 없었지만)

Pasted image 20230804220603.png
잘 뜨는 것을 확인하였다. yeah~~~ (한편 ACI는 무료로 영문 DNS를 지정해줘서 사용하기 편하다.)

마지막으로, 아까 건드렸던 AKS 배포를 위한 쿠버네티스 manifest 파일에서 containerPort80으로 다시 지정하면

Pasted image 20230804220959.png
AKS에서도 잘 뜨는 것을 볼 수 있다.

마치며

이로서 DevOps 여정을 마친다!
이제 다음 과제는 MLOps이다 😎