9 분 소요


오라클 쿠버네티스 배포 환경 구성하기-2


오늘은 지난 글에 이어서 쿠버네티스 클러스터에 구성했던 어플리케이션을 배포하고, Jenkins와 연결하는 구성을 해보겠습니다.

오라클 쿠버네티스 클러스터가 만들어져 있어야하며, Jenkins 설정도 완료되어 있음을 가정하겠습니다.

  • 오라클 클라우드에 무료 관리형 쿠버네티스 서버 구성하기: 링크
  • 간단한 React 앱 구성 후 OKE에 배포하기: 링크
  • 오라클 쿠버네티스 CI,CD 배포환경 구성하기-1: 링크


OCI native Ingress Controller 설치


생성된 OKE에 여러개의 서비스를 배포하고, 1개의 로드밸런서에서 경로에 따라 올바른 파드로 연결하는 등의 기능을 위해 Ingress Controller를 설정해야합니다.

많이 사용하는 Nginx Ingress Controller의 경우, 2026년 3월 지원이 종료되어 더이상 버그 패치, 보안 패치 등이 지원되지 않으므로 OCI 의 Ingress Controller를 설치해보겠습니다.


Pre-requirements 확인


OCI native ingress controller github를 보면 아래의 조건을 만족해야 OCI Ingress Controller를 설치할 수 있습니다.

  • 지원 K8s 버전: 1.27 ~ 1.32
  • 네트워크 플러그인(CNI): Native Pod Networking(NPN) 또는 Flannel CNI 사용 환경

우리 구성환경은 K8s 1.32 버전을 사용하며, Native Pod Networking을 사용하고 있으므로 사전 요구사항을 만족하며 다음 설정을 확인합니다.


Cert Manager 설치


인그레스 컨트롤러가 사용하는 웹훅(Webhook)의 TLS 인증서 관리를 위해 cert-manager가 배포되어 있어야 합니다.

OCI CLI가 설치되어 있고, OKE와 연결된 환경에서 아래의 명령어로 Cert Manager를 배포합니다.

저는 앞으로 배포를 담당할 CI/CD VM에서 실행하였습니다.

아래 명령을 실행하는데 있어, CICD VM => OKE Cluster 구간의 방화벽 설정으로 인해 오류가 발생하여 KUBERNETES_APIENDPOINTS_SecurityList 방화벽 규칙에 Ingress Rule로 6443 포트 TCP 통신에 대한 Ingress Rule을 추가하고 진행하였습니다.

kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml
kubectl get pods -n cert-manager # 설치 후 POD가 정상 동작하는지 확인

install_certmanager

위 이미지와 같이 설치 및 POD가 정상적으로 동작함을 확인한 뒤 다음으로 넘어갑니다.


IAM 정책 추가


OCI 콘솔에서 OKE 워커 노드(또는 동적 그룹/사용자)가 로드밸런서 및 네트워크 자원을 관리할 수 있도록 다음 정책을 추가합니다.

삽질 후기:
Dynamic Group 생성 및 Policy 추가는 아래와 같이 따라하면 되지만, 저는 결국엔 아래 방법으로 해결되지 않아, User 방식으로 수정하여 적용하였습니다.
같은 방법으로 적용을 원하시면 아래 Dynamic Group, Policy 관련 섹션은 건너뛰셔도 됩니다.


Dynamic Group 생성


set_dynamic_group1

위 이미지와 같이 OCI 콘솔에서 Identity & Security > Domains 메뉴를 찾아 들어간 뒤, 내 도메인 리소스가 위치하는 컴파트먼트로 변경하여 도메인 리소스 상세 페이지로 이동합니다.

set_dynamic_group2

이후 Dynamic Groups 탭을 클릭한 뒤, Create Dynamic groups 버튼을 클릭하여 신규 Dynamic group을 생성합니다.

set_dynamic_group3

생성할 다이나믹 그룹의 이름과 설명을 입력한 뒤, 설정할 Rule을 아래의 양식으로 입력합니다.

instance.compartment.id = 'ocid1.compartment.oc1..내컴파트먼트OCID'

적용할 compartment의 OCID는 OCI 콘솔의 Identity & Security > Compartments 메뉴에서 compartment를 선택하면 확인할 수 있습니다.


Policy 생성


create_policy

이번에는 OCI 콘솔에서 Identity & Security > Policies 메뉴를 찾아 들어간 뒤, 생성할 compartment를 선택한 뒤 Create Policy로 적용할 정책을 생성합니다.

create_policy2

마찬가지로 Policy 이름과 설명을 입력한 뒤, Show manual editor 버튼을 클릭하고, 본인의 리소스에 맞게 아래의 텍스트를 수정해서 정책을 생성합니다.

subject에서는 생성한 dynamic group의 이름을 입력하고, compartment-id는 정책을 적용할 compartment의 이름을 입력합니다.

이미지상에서는 콘솔에서 prd-mlplatform compartment를 선택하였으나, 적용할 compartment보다 상위 compartment를 선택한 상태에서 정책 생성이 되어야 하기에 root로 변경 후 생성하였습니다.

Allow dynamic-group <subject> to manage load-balancers in compartment id <compartment-id>
Allow dynamic-group <subject> to use virtual-network-family in compartment id <compartment-id>
Allow dynamic-group <subject> to manage cabundles in compartment id <compartment-id>
Allow dynamic-group <subject> to manage cabundle-associations in compartment id <compartment-id>
Allow dynamic-group <subject> to manage leaf-certificates in compartment id <compartment-id>
Allow dynamic-group <subject> to manage leaf-certificates-versions in compartment id <compartment-id>
Allow dynamic-group <subject> to read leaf-certificate-bundles in compartment id <compartment-id>
Allow dynamic-group <subject> to manage certificate-associations in compartment id <compartment-id>
Allow dynamic-group <subject> to read certificate-authorities in compartment id <compartment-id>
Allow dynamic-group <subject> to manage certificate-authority-associations in compartment id <compartment-id>
Allow dynamic-group <subject> to read certificate-authority-bundles in compartment id <compartment-id>
ALLOW dynamic-group <subject> to read public-ips in tenancy
ALLOW dynamic-group <subject> to manage floating-ips in tenancy
Allow dynamic-group <subject> to manage waf-family in compartment id <compartment-id>
Allow dynamic-group <subject> to read cluster-family in compartment id <compartment-id>
Allow dynamic-group <subject> to use tag-namespaces in tenancy


OCI Ingress Controller 설치


Helm 설치


이제 OCI Ingress Controller를 설치하기 위해 먼저 Helm을 설치합니다.

Helm 공식 문서에 있는 Ubuntu 부분을 찾아 아래 명령어를 수행하여 CICD 장비에 Helm을 설치합니다.

HELM_BUILDKITE_APT_KEY_ID="DDF78C3E6EBB2D2CC223C95C62BA89D07698DBC6"

sudo apt-get install curl gpg apt-transport-https --yes

curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey > "${TMPDIR:-/tmp}/helm.gpg"

# Ensure that the key ID matches to prevent a repository compromise from establishing an attacker controlled key
if [ "$(gpg --show-keys --with-colons "${TMPDIR:-/tmp}/helm.gpg" | awk -F: '$1 == "fpr" {print $10}' | head -n 1)" != "${HELM_BUILDKITE_APT_KEY_ID}" ]; then echo "ERROR: Unexpected Helm APT key ID: potential key compromise"; exit 1; fi

cat "${TMPDIR:-/tmp}/helm.gpg" | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list

sudo apt-get update
sudo apt-get install helm


secret 생성


이제 oci-native Ingress Controller를 클론해 옵니다.

git clone https://github.com/oracle/oci-native-ingress-controller.git
cd oci-native-ingress-controller

삽질 후기:
위에서 설정한 dynamic group, policy가 맞다면 instance 방식으로 정상 동작해야하지만 계속하여 Auth 관련 오류가 발생하였습니다.
여러번의 삽질 끝에 저는 user 기반으로 인증을 적용하였습니다.

user 기반으로 OCI API에 access 할 수 있도록 oci-native-ingress-controller 폴더 하위에 user-auth-config.yaml 파일을 아래와 같이 작성하였습니다.

#
# OCI Native Ingress Controller
#
# Copyright (c) 2023 Oracle America, Inc. and its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
#
auth:
  region: ap-chuncheon-1
  passphrase: 
  user: ocid1.user.oc1..aaaaaaaa_example # oci 콘솔 확인
  fingerprint: 67:d9:74:4b:21:example # oci 콘솔 확인 (User - API Key 탭)
  tenancy: ocid1.tenancy.oc1..aaaaaaaa_example # oci 콘솔 확인

이후 아래의 명령어로 secret을 생성해줍니다.

kubectl create namespace native-ingress-controller-system
kubectl create secret generic oci-config \
         --from-file=config=user-auth-config.yaml \
         --from-file=private-key=/home/ubuntu/.oci/oci_api_key.pem \ # 이전에 발급해둔 oci 계정의 api key
         --namespace native-ingress-controller-system

values.yaml 파일 수정


이제 oci-native-ingress-controller/helm/oci-native-ingress-controller/values.yaml 파일에서 각 ocid를 찾아 입력합니다. subnet의 경우, LB를 컨트롤 할 수 있도록 LB가 위치할 LoadBalancer Subnet의 OCID를 입력하였습니다.

위에서 user 정보로 secret을 만들었으니, authType의 값도 instance에서 user로 변경해줍니다.

edit_values

values.yaml 파일이 작성 완료된 후에는 아래 명령어로 values.yaml 파일과 oci-config secret 기반으로 ingress controller를 설치합니다.

helm install oci-native-ingress-controller helm/oci-native-ingress-controller

Ingress Controller 설치 과정에서 정말 애먹었는데, Dynamic Group 생성 후 Policy 생성까지만 하면 Request Endpoint: GET https://containerengine.ap-chuncheon-1.oci.oraclecloud.com/20180222/clusters/ocid1.cluster.oc1.ap-chuncheon-1.aaaa... 에러 메시지와 함께 정상 동작하지 않습니다.

cluster-family에 대한 manage 권한을 줘도 마찬가지였고, 결국에는 authType을 instance가 아닌 user로 줘야 정상 동작하게 되었습니다.

같은 이슈가 있으신 분들은 authType user 기준으로 secret 생성 후 values.yaml 파일을 작성하고 helm 으로 설치하시기를 바랍니다.

정상 설치가 되면 아래 명령어로 ingress contoller가 정상 running 하는 것을 확인할 수 있습니다.

kubectl get pods -n native-ingress-controller-system -o wide -w


OCI Ingress Controller 설정


우선 Ingress Controller를 설정하기 전에 기존에 배포해뒀던 nginx 관련 리소스들을 삭제합니다.

kubectl delete deployment nginx-deployment -n default
kubectl delete service nginx-service -n default
kubectl get deployments,services,pods,ingress

그리고 1개의 LB를 공용으로 사용하며, 여러개의 namespace로 나뉘어진 서비스들에 라우팅 해줄 수 있도록 아래와 같이 native-ic-ingress-params.yaml 파일을 작성합니다.

#
# OCI Native Ingress Controller
#
# Copyright (c) 2023 Oracle America, Inc. and its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
#
apiVersion: "ingress.oraclecloud.com/v1beta1"
kind: IngressClassParameters
metadata:
  name: native-ic-ingress-params
  namespace: native-ingress-controller-system
spec:
  compartmentId: "LB를 배포할 compartment의 ocid"
  subnetId: "LB를 배포할 subnet의 ocid"
  loadBalancerName: "native-ingress-controller-lb"
  isPrivate: false
  maxBandwidthMbps: 10
  minBandwidthMbps: 10

이제 작성된 native-ic-ingress-params.yaml 파일을 적용합니다.

kubectl apply -f native-ic-ingress-params.yaml
kubectl get ingressclassparameters -n native-ingress-controller-system

이후 아래와 같이 native-ic-ingress-class.yaml 파일을 작성합니다.

#
# OCI Native Ingress Controller
#
# Copyright (c) 2023 Oracle America, Inc. and its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
#
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: native-ic-ingress-class
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
spec:
  controller: oci.oraclecloud.com/native-ingress-controller
  parameters:
    scope: Namespace
    namespace: native-ingress-controller-system
    apiGroup: ingress.oraclecloud.com
    kind: ingressclassparameters
    name: native-ic-ingress-params

아래 명령어로 ingress class도 생성합니다.

kubectl apply -f native-ic-ingress-class.yaml
kubectl get ingressclass -n native-ingress-controller-system


front 앱 수동 배포


이제 ingress 설정이 마무리되었으니 지난번에 만들어둔 react 앱을 수동으로 배포해봅니다.


OCI Container Registry 생성


create_container_registry

OCI 콘솔에서 Developer Services > Container Registry 메뉴에서 Create repository 버튼을 클릭하여 Container Registry를 생성합니다.

create_container_registry2

Repository 명은 GitHub의 Organization / Repo 명을 따라 작명해주었고, Private Registry로 생성해주었습니다.


인증 토큰 생성


생성된 Container Registry에 이미지를 업로드할 수 있도록 인증 토큰에 대해 발급이 필요합니다.

OCI 콘솔의 My Profile 화면의 Tokens and keys 탭에서 Auth Token 을 찾아, Generate token을 클릭하여 생성해줍니다.

create_container_registry2

description을 저장한채로 생성 버튼을 클릭하면 나오는 key를 잃어버리지 않게 잘 보관합니다.

이제 docker cli로 컨테이너 레지스트리에 로그인합니다.

docker login yny.ocir.io

춘천지역의 코드가 yny이므로 위 명령어로 생성되었고, 다른 지역이라면 지역코드를 찾아 바꿔주어야 합니다.

로그인시에 사용자 ID는 테넌시ID/username로 입력해야하며, 테넌시 ID는 oci os ns get으로 확인할 수 있고, username은 OCI 콘솔의 MyProfile에서 확인할 수 있습니다.

정상적으로 Login Succeeded 로그를 확인한 후에는 이미지를 빌드합니다.


Docker 이미지 준비


repo 안에 아래와 같이 docker file을 생성해서 원격 저장소에 push 해줍니다.

# 1단계: Node 빌드 환경 (플랫폼 명시 가능하도록 구조화)
FROM --platform=$BUILDPLATFORM node:20-alpine AS build
WORKDIR /app

COPY package*.json ./
# ARM 네임스페이스 컴파일 속도 향상 및 불필요한 캐시 제거
RUN npm ci --quiet

COPY . .
RUN npm run build

# 2단계: 호스트와 아키텍처가 일치하는 가벼운 Nginx 아키텍처로 정적 파일 이관
FROM --platform=$TARGETPLATFORM nginx:alpine
RUN rm -rf /etc/nginx/conf.d/default.conf
RUN echo 'server { listen 80; location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; } }' > /etc/nginx/conf.d/default.conf

COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

CICD VM은 x86기반 아키텍처의 장비이지만, OKE는 ARM 기반 아키텍처 장비로 만들어둔 관계로 멀티 아키텍처용 빌더를 구성한 뒤 docker file을 빌드하여 이미지를 만들어줍니다.

# 빌드킷(BuildKit) 핵심 이미지 미리 다운로드
docker pull moby/buildkit:buildx-stable-1

# 1. 멀티 아키텍처용 빌더 생성
docker buildx create --name mybuilder --use

# 2. 빌더 기동 및 상태 확인
docker buildx inspect --bootstrap

# 3. docker 이미지 빌드
docker buildx build \
  --platform linux/arm64 \
  -t mlplatform/fe-www:v1.0.0 \
  --load .

# 4. 빌드된 이미지 확인
docker images

# 5. docker tag 변경
docker tag mlplatform/fe-www:v1.0.0 yny.ocir.io/<테넌시명>/mlplatform/fe-www:v1.0.0

# 6. OCIR에 push
docker push yny.ocir.io/<테넌시명>/mlplatform/fe-www:v1.0.0

create_container_registry2

push가 잘 완료된 이후에는 OCI 콘솔에서 image를 확인할 수 있습니다.


쿠버네티스 배포


docker registry secret 생성


이제 위에서 테스트한 내용을 바탕으로 OKE 클러스터에 OCR에 접근할 수 있는 정보를 secret으로 만들어 저장합니다.

kubectl create secret docker-registry ocirsecret --docker-server=yny.ocir.io --docker-username='테넌시id/username' --docker-password='token'
kubectl get secrets


ingress yaml 작성 및 적용


정상적으로 secret이 만들어졌으니, 클러스터 배포를 위해 namespace를 생성하고 적용합니다.

kubectl create namespace mlplatform
kubectl create secret docker-registry ocirsecret --docker-server=yny.ocir.io --docker-username='테넌시id/username' --docker-password='token' -n mlplatform # 새로 생성한 namespace에도 secret 생성

이후 아래와 같이 fe-www 서비스를 배포하기 위한 yaml 파일을 작성합니다.

# 1. 배포 (Deployment)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fe-www-deployment
  namespace: mlplatform
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fe-www
  template:
    metadata:
      labels:
        app: fe-www
    spec:
      containers:
      - name: fe-www-container
        image: yny.ocir.io/axqyrowq4jay/mlplatform/fe-www:v1.0.0
        ports:
        - containerPort: 80
      imagePullSecrets:
      - name: ocir-secret # 아까 만든 시크릿 이름
---
apiVersion: v1
kind: Service
metadata:
  name: fe-www-svc
  namespace: mlplatform
spec:
  selector:
    app: fe-www  # 이 부분이 Deployment의 matchLabels와 일치해야 합니다!
  ports:
    - protocol: TCP
      port: 80      # 인그레스에서 참조할 포트
      targetPort: 80 # 컨테이너 내부 포트

위에서 작성한 yaml 파일을 아래 명령어로 작성하여, deployment와 service를 생성합니다.

kubectl apply -f fe-www.yaml
kubectl get deployments,services,pods -n mlplatform

잘 생성 된 것을 확인한 이후에는 마지막으로 native-ic-ingress-path-basic.yaml 파일을 작성하여 lb와 연결해줍니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: native-ic-ingress-path-basic
  namespace: mlplatform
spec:
  ingressClassName: native-ic-ingress-class
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: fe-www-svc
            port:
              number: 80

위 파일을 저장한 후 아래 명령어로 ingress 리소스를 생성해줍니다.

kubectl apply -f native-ic-ingress-path-basic.yaml

ingress 리소스 요약

저는 무료로 제공되는 리소스 내에서 여러개의 프로젝트를 진행할 수 있도록 ingress를 도입하였기에 아래와 같은 구조로 리소스를 위치했습니다.

이제 신규로 생성되는 네임스페이스가 있다면 마지막의 native-ic-ingress-path-basic.yaml 파일만 작성하여, 각 네임스페이스에 두고, 그 외 ingress 관련 리소스들은 native-ingress-controller-system 네임스페이스에서 관리됩니다.

ingress_structure

배포 확인

위 내용이 모두 완료되었다면, yaml 파일에 적용된 규칙을 따르는 Load Balancer가 OCI 콘솔에 생성되어 있는 것을 확인할 수 있습니다.

해당 LB의 Public IP 주소로 접근 하면, 미리 만들어 놓았던 Vite + React의 메인 페이지를 확인할 수 있습니다.

created_lb

deployed_app

이제 수동 배포는 정상적으로 수행하였고, 앱도 잘 올라가 있는 것을 확인하였으니, 다음 포스트에서는 jenkins가 배포 내용을 자동 식별해서 배포할 수 있도록 설정해보겠습니다.