본문 바로가기

Side Project

React+Flask 어플리케이션: 코드부터 Jenkins 배포까지 (CI/CD) - 3 (Ingress 활용하기)

728x90
반응형

안녕하세요! 

이 글에서는 https://silver-programmer.tistory.com/entry/ReactFlask-어플리케이션-코드부터-Jenkins-배포까지-CICD-2 에 이어서 Ingress를 이용해 Frontend + Backend를 하나의 도메인으로 배포하는 방법을 다루었습니다.

 

Ingress 필요성 및 설치 → Helm values 설정 → Ingress 설정 → 배포/검증 → 서비스 확인 → 도메인 접속(Mac/Linux) → 마무리

 

이번 블로그에서 사용할 프로젝트 구조는 아래와 같습니다.

ImageGallery/
├── backend/
│   ├── app/
│   │   ├── main.py
│   │   └── requirements.txt
│   └── Dockerfile
├── frontend/
│   ├── src/
│   │   ├── App.js
│   │   ├── Config.js
│   │   └── index.js
│   ├── package.json
│   └── Dockerfile
└── helm/
    └── image-gallery/             # Helm 차트 루트
        ├── Chart.yaml             # 차트 메타데이터
        ├── values-dev.yaml        # 개발 환경용 override values
        └── templates/             # 쿠버네티스 리소스 템플릿
            ├── backend-deployment.yaml
            ├── backend-ingress.yaml
            ├── frontend-deployment.yaml
            ├── frontend-ingress.yaml
            └── service.yaml

 

 

 


1. Ingress를 쓰는 이유

Ingress는 외부 트래픽을 클러스터 내부의 여러 서비스로 라우팅하는 역할을 합니다. (https://silver-programmer.tistory.com/entry/Kubernetes-서비스-port-와-ingress) 특히, NodePort 를 사용하면 매번 달라지는 포트 번호를 알고 요청을 보내야 하는데, Ingress 를 사용하면 도메인 기반으로 라우팅이 가능하죠.

즉, 백엔드와 프런트엔드 서비스가 따로 있을 때, 두 서비스를 하나의 도메인으로 묶어주는 것이 바로 Ingress의 핵심 기능입니다.


2. Ingress 설치

Ingress 를 사용하기 위해 먼저 Ingress 를 설치합니다. 이번 실습에서 minikube 를 사용합니다.

  • Ingress 설치 및 검증
minikube addons enable ingress # enable ingress
kubectl get pods -n ingress-nginx # verify ingress

 

아래와 같은 결과가 나옵니다.

NAME                                       READY   STATUS      RESTARTS      AGE
ingress-nginx-admission-create-z65xs       0/1     Completed   0             11h
ingress-nginx-admission-patch-w84j2        0/1     Completed   1             11h
ingress-nginx-controller-67c5cb88f-926xb   1/1     Running     1 (62m ago)   11h

3. Helm values 설정

프로젝트 helm 차트 구조는 아래와 같습니다.

helm/
└── image-gallery/                # Helm 차트 루트
    ├── Chart.yaml                # 차트 메타데이터
    ├── values.yaml               # 기본 values (공용 설정)
    ├── values-dev.yaml           # 개발 환경용 override values   
    └── templates/                # 쿠버네티스 리소스 템플릿
        ├── backend-deployment.yaml   
        ├── backend-ingress.yaml      
        ├── frontend-deployment.yaml  
        ├── frontend-ingress.yaml     
        └── service.yaml         
 

그럼 이제 Ingress 파일을 설정하기 위해 먼저 `values-dev.yaml` 파일을 아래와 같이 수정해봅니다.

backend:
  service:
    type: ClusterIP
    port: 8000

frontend:
  service:
    type: ClusterIP
    port: 3000

ingress:
  enabled: true
  host: myapp.local
  paths:
    backend: /api(/|$)(.*)
    frontend: /
  backendAnnotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2 # rewrite 규칙
  frontendAnnotations: {}

 

위와 같이 작성하면, http://myapp.local 도메인을 endpoint 로 해서 아래로 요청이 가게 됩니다.

  • /api 요청은 백엔드 서비스(port: 8000)
  • / 요청은 프론트엔드 서비스(port: 3000)

그리고, 위 파일에서 backendAnnotations, frontendAnnotations을 정의하였는데요, ingress의 다양한 속성을 정의할 수 있습니다.   rewrite는 요청 URL을 재작성(rewrite)하는 기능입니다.

예를 들어, myapp.local/api/users라는 요청이 들어왔을 때, /api 접두사를 제거하고 /users로 바꿔 백엔드에 전달하는 것이죠.

 

Kubernetes의 ingress-nginx 컨트롤러는 위에서 볼 수 있듯이 nginx.ingress.kubernetes.io/rewrite-target 어노테이션을 사용해 rewrite 기능을 제공합니다.

 

  • path: /api(/|$)(.*): 이 부분은 정규 표현식으로 api 뒤에 오는 모든 문자열을 캡처한다는 의미에요.
  • nginx.ingress.kubernetes.io/rewrite-target: /$2: 여기서 /$2는 캡처된 두 번째 그룹의 내용을 재작성된 URL의 대상으로 사용하겠다는 뜻입니다. 위 예시에서 (.*)가 두 번째 그룹이므로, /api/users 요청은 /users로 변경됩니다.

즉, rewrite는 사용자가 보는 URL은 그대로 두면서, 백엔드 서비스가 처리하기 편한 형태로 내부적인 URL 경로를 수정하는 기능이라고 이해하면 됩니다.

 

 

추가로, 백엔드 url을 사용하기 위해서 프론트엔드의 config.js 파일을 아래처럼 수정합니다. 

export const BACKEND_URL =
  process.env.REACT_APP_BACKEND_URL || "http://myapp.local/api";

4. Ingress 설정: 프런트엔드와 백엔드

저는 처음에 Ingress 파일을 프론트엔드와 백엔드를 통합해서 아래처럼 하나의 파일로 작성했는데요... 이거 때문에 routing 이 제대로 되지 않아 고생했습니다 ㅎㅎㅎ🥲

 

이는 rewrite 규칙을 백엔드에만 적용하고 싶은 의도와 다르게 프런트엔드에도 영향을 주어서 인데요,

rewrite 규칙을 백엔드에만 적용하기 위해서는 하나의 Ingress 리소스를 두 개로 분리하는 것이 가장 좋은 해결책입니다.

# 통합된 Ingress

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Chart.Name }}-ingress
  annotations:
    {{- range $key, $value := .Values.ingress.annotations }}
    {{ $key }}: {{ $value }}
    {{- end }}
spec:
  rules:
    - host: {{ .Values.ingress.host }}
      http:
        paths:
          - path: {{ .Values.ingress.paths.backend }}
            pathType: ImplementationSpecific
            backend:
              service:
                name: {{ .Chart.Name }}-backend
                port:
                  number: {{ .Values.backend.service.port }}
          - path: {{ .Values.ingress.paths.frontend }}
            pathType: Prefix
            backend:
              service:
                name: {{ .Chart.Name }}-frontend
                port:
                  number: {{ .Values.frontend.service.port }}
{{- end }}

 

위 파일을 백엔드, 프론트엔드로 분리합니다.


백엔드 Ingress (API)

rewrite 규칙은 백엔드 API 경로에만 적용합니다. 

pathType: ImplementationSpecific을 사용해 rewrite-target 어노테이션이 올바르게 동작하도록 명시하는 것이 중요합니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backend-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx # ✨ 이 부분은 꼭 필요해요!
  rules:
  - host: myapp.local
    http:
      paths:
      - path: /api(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: backend-service
            port:
              number: 80

프런트엔드 Ingress (UI)

프런트엔드용 Ingress에는 rewrite 규칙을 포함하지 않습니다. path: /와 같이 기본 경로를 라우팅하도록 설정합니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: frontend-ingress
spec:
  ingressClassName: nginx # ✨ 여기에도 필요합니다!
  rules:
  - host: myapp.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

 

나머지 deployment.yaml, service.yaml 등의 helm 파일들은 이전 포스팅과 동일합니다.

 

참고로, 백엔드 ingress는 Prefix가 아닌 ImplementationSpecific을 사용하는 이유는요,

rewrite-target 어노테이션은 NGINX Ingress Controller의 특정 기능입니다.

  • pathType: Prefix는 Kubernetes Ingress 표준 스펙에 따라 접두사 매칭만 처리하도록 설계되었습니다.
  • pathType: ImplementationSpecific은 컨트롤러가 제공하는 비표준 기능을 사용하겠다는 것을 명시적으로 알리는 용도입니다.

nginx.ingress.kubernetes.io/rewrite-target와 같이 경로의 일부를 잘라내거나 정규 표현식의 캡처 그룹(예: $2)을 사용하는 기능은 표준에 속하지 않습니다. 따라서 ImplementationSpecific을 사용하면 해당 컨트롤러가 이 경로에 대해 특별한 처리를 해야 한다는 것을 이해하게 됩니다. 

 

즉, rewrite 규칙을 사용해 복잡한 경로 조작이 필요하다면 pathType: ImplementationSpecific을 사용하는 것이 최상의 실무 관행입니다. 이는 코드의 의도를 명확하게 하고, 잠재적인 호환성 문제를 방지하며, Ingress 설정의 안정성을 높여줍니다.


5. 실행 & 검증

이전 포스팅에서 Jenkins 를 이용하여 CI/CD 를 구축했었는데요, Helm 파일을 수정해서 commit&push 하면 자동으로 Jenkins 가 트리거되어 빌드를 수행하고, ingress 가 적용된 파드가 뜨게 됩니다.

 

만약 Jenkins 를 구축하지 않았다면 아래 명령어를 통해 helm 을 적용합니다.

helm upgrade --install image-gallery ./helm/image-gallery -f ./helm/image-gallery/values-dev.yaml
  • helm upgrade --install
    • helm upgrade: 이미 배포된 릴리스를 새 설정으로 업데이트
    • --install: 릴리스가 없으면 새로 설치 -> 즉, “없으면 설치하고 있으면 업데이트”하는 명령
  • image-gallery
    • Helm 릴리스 이름 (Chart.yaml에 정의된 name)
    • 쿠버네티스에 생성될 Deployment, Service, Ingress 리소스들의 접두사로 사용됨 -> 예: image-gallery-frontend, image-gallery-backend
  • ./helm/image-gallery
    • Helm 차트 디렉토리 경로
    • Chart.yaml, values.yaml, templates/ 가 들어있는 폴더
  • -f ./helm/image-gallery/values-dev.yaml
    • values 파일 지정
    • Helm 차트에서 사용하는 변수(.Values)를 override 하는 용도

 

5. 서비스 주소 확인하기

아래와 같이 서비스 정보를 얻고, minikube 명령어를 통해 어떤 Ip와 포트로 서비스가 배포되었는지 확인할 수 있습니다.

kubectl get svc # 서비스 확인
minikube service image-gallery-frontend --url # 서비스 배포된 주소 확인

 

아래처럼 결과가 나옵니다. http://127.0.0.1:50059 로 브라우저에서 접속하면 프론트엔드 화면을 볼 수 있습니다.


6.  도메인 주소로 접속하기

이제 위의 Ingress.yaml에서 정의한 myapp.local 도메인으로 접속하여 어플리케이션에 접속해보겠습니다.

 

여기서 주의할 점이 있습니다!! 저는 Mac 을 사용해서 ingress 를 테스트했는데, 아무리 해도 접속이 되지 않아서...  공식 홈페이지를 찾아보니 아래와 같은 설명이 있습니다.

Note:
The network is limited if using the Docker driver on MacOS (Darwin) and the Node IP is not reachable directly. To get ingress to work you’ll need to open a new terminal and run 
minikube tunnel
sudo permission is required for it, so provide the password when prompted.

 

정리하면, MacOS + Docker 드라이버 = 네트워크가 분리되어 있어서 Ingress가 바로 안되므로 (MacOS에서는 Docker Desktop이 가상화 계층을 한 번 더 감싸고 있음)

별도 terminal을 열어서 minikube tunnel 명령을 실행시킴으로써 minikube tunnel 명령어는 클러스터의 서비스에 외부에서 접근 가능한 터널을 생성해 줍니다.

minikube tunnel

 

 

네 ㅎㅎ 이제 브라우저에 접속하기 위해서 /etc/hosts 파일을 수정하여 myapp.local 도메인과 ip 주소를 mapping 해 줍니다. 

  • hosts 파일 수정: kubectl get ingress 명령어를 실행하여 ADDRESS 필드에 할당된 IP를 확인합니다. (혹은 minikube ip 명령어 사용) 이 IP와 Ingress 도메인(myapp.local)을 로컬 PC의 hosts 파일에 매핑합니다. 
    • 그런데!! 이는 Linux 기준입니다... ㅎㅎ 
  • minikube tunnel은 기본적으로 127.0.0.1로 터널링하므로, IP 주소는 127.0.0.1로 설정해야 합니다.
# /etc/hosts

127.0.0.1 myapp.local # Mac

# 192.168.49.2 myapp.local # Linux

 

드디어 끝났습니다.... 🥹

  • 접속: 이제 http://myapp.local로 접속하여 애플리케이션을 테스트할 수 있습니다. minikube tunnel 명령어는 터미널이 열려 있는 동안만 유효하니, 터미널을 닫지 않도록 주의하세요.

[번외] Ingress 완벽 설정 체크리스트

 

  • Ingress 리소스 분리: Frontend와 Backend Ingress를 각각 생성했는가?
  • ingressClassName 명시: 두 Ingress 리소스 모두 spec.ingressClassName: nginx를 포함했는가?
  • rewrite 규칙: rewrite 어노테이션과 ImplementationSpecific pathType을 적용했는가?
  • minikube tunnel 실행: minikube tunnel 명령어를 새 터미널에서 실행하고 유지하고 있는가?
  • hosts 파일 수정: Ingress의 HOSTS 도메인을 127.0.0.1에 올바르게 매핑했는가?

 

 

그리고 아래 링크는 kind 를 사용할 때 ingress 를 어떻게 배포할 수 있는지 (mac 기준) 정리된 글입니다. kind를 사용하시는 분들에게 도움이 될 것 같아 첨부드립니다.

https://medium.com/groupon-eng/loadbalancer-services-using-kubernetes-in-docker-kind-694b4207575d

 

LoadBalancer Services using Kubernetes in Docker (kind)

Kubernetes is a very popular open-source container orchestration system for deploying, scaling, and updating your software, as well as…

medium.com

 


[참고]

Ingress - minikube 사용 공식문서

https://kubernetes.io/docs/tasks/access-application-cluster/ingress-minikube/

 

Set up Ingress on Minikube with the NGINX Ingress Controller

An Ingress is an API object that defines rules which allow external access to services in a cluster. An Ingress controller fulfills the rules set in the Ingress. This page shows you how to set up a simple Ingress which routes requests to Service 'web' or '

kubernetes.io

 

728x90
반응형