k8s & 어플리케이션 배포 & k8S + CI/CD 흐름 과정 담기 ... (2)

fearisthemindkiller·2024년 10월 24일

k8s에 어플리케이션 띄워보는 방식으로 가면서 리소스에 대해 좀 더 세세하게 알아보는 과정 거쳐볼 예정.
그 이후엔 실제 ci/cd는 보통 어떻게 하는지만 정리할 것이다.

orbstack v 1.7.5

Orbstack은 MacOS에서 Docker 컨테이너, 쿠버네티스, 각종 Linux 배포본을 빠르고 간편하게 실행하는 Docker Desktop 대체제다

유명한 docker desktop 대신 왜 orbstack을 쓰냐? 이제 다 알겠지만... docker desktop은 유료화가 됐고... 회사에서 쓰다보니 익숙해졌고... CPU랑 메모리 사용량이 도커 데스크탑보다 낮음. 그리고 도커 데스크탑의 기능을 대부분 대체할 수 있다보니 사용하게 됐다.

차이점이나 장점 등 자세한 내용은 아래 링크를 참고하면 좋겠네..

https://jonnung.dev/docker/2023/11/20/orbstack-alternative-docker-desktop/

어쨌든,

orbstack에서 가벼운 싱글 노드 쿠버네티스 클러스터를 띄우는 걸 지원해주기 때문에 메뉴에서 kubernetes를 눌러 turn on 해주면 위의 캡쳐처럼 돌아감. (나는 이미 띄워놔서 캡처는 생략 함)

docker ps
쿠버네티스가 올라간 것을 확인할 수 있음

테스트로 이미 orbstack에서 알려주는 example 명령어를 사용해보자.

kubectl run nginx --image=nginx
kubectl expose pod nginx --type=NodePort --port=80

를 입력하면
nginx 이미지로 된 pod가 생성되고, service 는 Nodeport로 설정해서 port를 통해 외부에서 접근할 수 있도록 설정해 줌.
지금 pod와 service를 말했는데 이젠 게시물에서 언급한 리소스임.

그러면

다음과 같이 orbstak에서 확인할 수 있음. port는 80 -> 32700으로 설정됐으므로
localhost:37200으로 들어가보면

다음과 같이 쿠버네티스로 띄운 nginx에 NortPort설정을 통해 접근할 수 있다.

신기하죵?

그럼 실제 어플리케이션을 올려보겠다.
이 때 kubectl로 리소스 매니패스트 파일을 사용하여 pod를 올려보려고 한다.
서비스는 총 2가지이고, spring boot로 정말 간단하게 만든 서버 2대를 올릴 예정이다.

애플리케이션 간단 소개

공통
- java 21
- spring boot 3.3.4
- maven

띄울 애플리케이션이 있어야 하는데... 막상 올릴 서비스는 없어서 k8s에 올려서 테스트 해볼 정도의 서비스 2개를 만들었다.

🎧 kwangya-service ( 그 에스파 광야 맞다 .. )

  • [GET] / : 서비스명 가져오기 (kwangya-service)
  • [GET] /{group}/member : 특정 그룹의 모든 멤버 목록 가져오기
    이 때, group으로 aespa를 넣어 호출하면 openfeign을 통해 aespa-servcie와 통신하여 모든 멤버 데이터를 가져올 수 있게 함.

👩‍👩‍👧‍👧 🖤 aespa-service ( 그 에스파가 맞다 .. )

  • [GET] /member : 에스파 모든 멤버 목록 가져오기
  • [GET] /member/{id} : id로 에스파 멤버 가져오기

로 정말 대~충 통신만 우선 할 수 있게 간단한 REST API 서버를 만들어놨다.

이제
k8s에서 이 서비스들이 어떻게 통신할 거냐면

이런 식으로
1. 외부에서 kwangya-service 와 aespa-service 를 둘 다 각각 호출할 수 있도록
2. 외부에서 kwangya-service를 호출하면 kwangya-service 내부에서 aespa-service를 호출할 수 있도록

처리해볼 예정이다.

  1. 각 서비스 빌드 및 패키징

mvn clean compile package -Dmaven.test.skip=true

2.각 서비스에 dockerfile 생성

FROM openjdk:17-ea-11-jdk-slim
VOLUME /tmp
COPY target/{패키징된 jar 파일} app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

target 폴더를 들어가보면 패키징된 jar 파일을 확인할 수 있음. 그 파일의 이름을 넣어주면 된다.
(kwangya-service-0.0.1-SNAPSHOT.ja | aespa-service-0.0.1-SNAPSHOT.jar)
잘 실행되는지 알고 싶으면 java -jar kwangya-service-0.0.1-SNAPSHOT.jar 로 띄워보면 됨.

  1. dockerfile 실행해 이미지 생성

docker build --tag kwanya-service:1.0 .
docker build --tag aespa-service:1.0 .

그럼 각자 이런식으로 도커 이미지가 만들어진다.

  1. 도커 이미지 확인


그럼 이제 k8에 올리기 위한 준비를 한다.
필요 리소스 매니패스트 파일

1. Deployment : kwangya-service

쿠버네티스 공식 문서에서 설명하는 deployment
디플로이먼트(Deployment) 는 파드와 레플리카셋(ReplicaSet)에 대한 선언적 업데이트를 제공한다.
디플로이먼트에서 의도하는 상태 를 설명하고, 디플로이먼트 컨트롤러(Controller)는 현재 상태에서 의도하는 상태로 비율을 조정하며 변경한다. 새 레플리카셋을 생성하는 디플로이먼트를 정의하거나 기존 디플로이먼트를 제거하고, 모든 리소스를 새 디플로이먼트에 적용할 수 있다.
https://kubernetes.io/ko/docs/concepts/workloads/controllers/deployment/

kangya-deployment.yaml

apiVersion: apps/v1
kind: Deployment						# 리소스 종류 - deployment
metadata:
  name: kwangya-deployment				# deployment 이름
spec:
  selector:								# 파드 생성 spe
    matchLabels:
      app: kwangya-service-app
  replicas: 1							# 복제 수
  template:
    metadata:
      labels:
        app: kwangya-service-app		# 파드 레벨 = selector 이름과 동일
    spec:
      containers:
        - name: kwangya-service			# 컨테이너 이름
          image: kwangya-service:1.0	# 컨테이너 이미지
          imagePullPolicy: Always		# 이미지 항상 pull 함
          ports:
            - containerPort: 8090		# 컨테이너가 사용하는 포트
              protocol: TCP				# TCP 통신
          resources:
            requests:
              cpu: 500m					# 컨테이너에서 요청하는 CPU
              memory: 1000Mi			# 컨테이너에서 요청하는 메모리
          env:
            - name: AESPA-SERVICE-URL	# 환경 변수 이름
              valueFrom:
                configMapKeyRef:
                  name: kwangya-configmap	# 참조하는 configmap 이름
                  key: aespa-service-url	# configmap에서 가져올 키
---
apiVersion: v1
kind: Service							# 리소스 종류 - service
metadata:
  name: kwangya-service					# service 이름
spec:
  type: NodePort						# 외부에서 접근 가능하게 함
  selector:
    app: kwangya-service-app
  ports:
    - protocol: TCP
      port: 8090						# 서비스에서 사용할 포트
      targetPort: 8090					# 파드에서 사용하는 포트
      nodePort: 30000					# 외부에서 접근할 수 있도록 하는 포트

2. Deployment : aespa-service

kwangya-service 와 비슷하지만, 우선 환경변수 설정 따로 안함.
그리고 포트가 다름 8091, 30001

aespa-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: aespa-deployment
spec:
  selector:
    matchLabels:
      app: aespa-service-app
  replicas: 1
  template:
    metadata:
      labels:
        app: aespa-service-app
    spec:
      containers:
        - name: aespa-service
          image: aespa-service:k8s_v1.0
          imagePullPolicy: Always
          ports:
            - containerPort: 8091
              protocol: TCP
          resources:
            requests:
              cpu: 500m
              memory: 1000Mi
---
apiVersion: v1
kind: Service
metadata:
  name: aespa-service
spec:
  type: NodePort
  selector:
    app: aespa-service-app
  ports:
    - protocol: TCP
      port: 8091
      targetPort: 8091
      nodePort: 30001

여기서 중요한 건 service 리소스인데,

kwangya-service 8090은 k8s 내부에서 사용하는 포트이고, nodeport의 30000은 k8s 외부에서 서비스를 호출할 때 사용하는 포트이다.

ports:
   - protocol: TCP
     port: 8090
     targetPort: 8090
     nodePort: 30000

aespa-service 도 똑같이 8091은 k8s 내부에서 사용하는 포트이고, nodeport의 30001은 k8s 외부에서 서비스를 호출할 때 사용하는 포트이다.

  ports:
    - protocol: TCP
      port: 8091
      targetPort: 8091
      nodePort: 30001
      

3. configmap

쿠버네티스 공식 문서에서 설명하는 configmap
configmap은 키-값 쌍으로 기밀이 아닌 데이터를 저장하는 데 사용하는 API 오브젝트이다. 파드는 볼륨에서 환경 변수, 커맨드-라인 인수 또는 구성 파일로 configmap을 사용할 수 있다.
configmap을 사용하면 컨테이너 이미지에서 환경별 구성을 분리하여, 애플리케이션을 쉽게 이식할 수 있다.즉, 환경 변수, 커맨드-라인 인수 또는 구성파일을 설정할 수 있다.
https://kubernetes.io/ko/docs/concepts/configuration/configmap/

apiVersion: v1
kind: ConfigMap
metadata:
  name: kwangya-configmap			# kwangya-service 용이 아닌 전체 config로 사용할 수 있음. 
data:
  aespa-service-url: "http://aespa-service:8091"

⭐️ 중요한 내용
kwangya-service에서 aespa-service로 rest api를 호출할 때, kwangya-service는 aespa-service의 서버를 알아야 한다.그리고, k8s에서는 aespa-service 는 8091 번호로 port를 설정해놨다.
쿠버네티스 클러스터 안에서는 파드끼리 통신이 가능하기 때문에 30001으로 외부로 나가서 호출하는 게 아닌 "http://aespa-service:8091" 로 호출할 수 있도록 설정해놓는 작업을 한다.

4. manifest 실행

  1. configmap 실행
    kubectl apply -f configmap.yml
  1. deployment 실행
    kubectl apply -f kwangya-deployment.yml
    kubectl apply -f aespa-deployment.yml

(이 때, kubectl도 설치해놔야 한다. 나는 맥이라서 brew install kubectl 로 설치 함.)
하지만 에러가 난다...
이유는 이미지를 이미지 레지스트리(docker hub 이나 프라이빗 레지스트리) 에 안올렸기 때문인데... dokcer hub에 올리기 싫어서(ㅋㅋㅋ) 노선을 틀어 minikube를 이용해서 쿠버네티스 클러스터 올릴 예정이다.

minikube 개념, 설치, 실행

minikube는 로컬 환경에서 쿠버네티스 클러스터 쉽게 띄울 수 있는 도구.
Minikube를 사용하면 로컬 컴퓨터에서 단일 노드 Kubernetes 클러스터를 실행하여 애플리케이션 개발, 테스트 및 배포를 쉽게 할 수 있으며, 단일 머신에서 다중 노드 Kubernetes 환경을 시뮬레이션하여 다양한 Kubernetes 기능 및 구성을 실험할 수 있습니다.
https://www.itmaya.co.kr/wboard/view.php?wb=tech&idx=52

1. 설치

나는 mac이므로 brew를 이용해서 설치한다.
brew install minikube

이러면 간단하게 설치 완료

minikube start

2. 실행

minikube start					# minikube 실행
kubectl cluster-info			# minikube랑 연결 잘 됐는지 확인
minikube status					# minikube 잘 떴는지 확인

로그를 보면 CPUs 2, memory=8100MB 인데 이건 옵션으로 변경할 수 있다. 보통 minikube는 local에서 테스트할 때 보통 사용하므로 필요에 따라 설정을 바꾸면 된다.

minikube dashboard : minikube를 대시보드로 보고 싶을 때 사용

그럼 자동으로 minikube addons enable metrics-server 가 애드온되면서 대시보드가 활성화된다.

로그에 있는 url 복사에서 브라우저 들어가면 됨.

+) 중지 / 삭제할 때는 아래와 같이 하면 된다.

minikube stop		# minikube 중지
minikube delete		# minikube 삭제

이제 minikube 쿠버네티스 클러스터가 만들어졌으니 다시 deployment와 configmap 실행.
이 때, 로컬에 있는 이미지를 사용해야 하므로 기존 이미지는 지우고 다시 빌드할 것이다.

  1. configmap 실행
    kubectl apply -f configmap.yml

대시보드에서 컨피그 맵 메뉴 들어가면 kwangya-configmap 생성 확인 가능.

  1. 기존 이미지 삭제 후, 아래와 같이 이미지 생성
 eval $(minikube docker-env)
 docker build --tag aespa-service:1.0 .
 eval $(minikube docker-env)
 docker build --tag kwangya-service:1.0 .
  1. deployment 수정
	(...)
    spec:
      containers:
        - name: kwangya-service
          image: kwangya-service:1.0
          imagePullPolicy: Never
          ports:
            - containerPort: 8090
              protocol: TCP
   (...)    

Always를 Never로 변경

https://stackoverflow.com/questions/42564058/how-can-i-use-local-docker-images-with-minikube

  1. deployment 실행
    kubectl apply -f kwangya-deployment.yml
    kubectl apply -f aespa-deployment.yml

이렇게 하면 로컬 이미지를 pull해서 k8s에 생성된 파드를 확인할 수 있다~

명령어로 확인하려면
kubectl get pods

Running으로 정상적으로 실행 됨.

API 호출해보기

kubectl get services 로 서비스 설정 잘 되어있는지 확인한다.

minikube에 올라가있기 때문에 minikube url 확인

minikube ip

따라서,
앞서 얘기한

🎧 kwangya-service
- [GET] / : 서비스명 가져오기 (kwangya-service)
- [GET] /{group}/member : 특정 그룹의 모든 멤버 목록 가져오기
	이 때, group으로 aespa를 넣어 호출하면 openfeign을 통해 aespa-servcie와 통신하여 모든 멤버 데이터를 가져올 수 있게 함.

👩‍👩‍👧‍👧 🖤 aespa-service
- [GET] /member : 에스파 모든 멤버 목록 가져오기
- [GET] /member/{id} : id로 에스파 멤버 가져오기

로 호출을 시도해본다.

  1. kwangya-service 호출 : mikube-ip:30000/
    curl --silent --location --request GET 'http://192.168.67.2:30000/'

  1. aespa-service 호출 : mikube-ip:30001/member
    curl --silent --location --request GET 'http://192.168.67.2:30001/member'

외부에서 각각 서비스에 호출 성공

  1. kwangya-service 호출 : mikube-ip:30000/aespa/member

이 때, kwangya-service 를 거쳐 내부적으로 aespa-service를 호출하여 aespa 모든 멤버 목록을 가져오는 흐름이다.

그런데 안된다.... 하

원인 : configmap에 포트 잘못 설정했음. 하하


8091로 변경하고 다시 설정

pods 다 죽이고 다시 실행.

호출 완료 ~~~

이렇게 k8s위에 테스트 완성 ...

다음 게시물은 그럼 실제로는 어떻게 ci/cd 구성하는지 정리해 볼 예정이다.

0개의 댓글