쿠버네티스 환경에 스프링 어플리케이션 배포하기

Introduction

  • 쿠버네티스 등장 이전, IT 자원을 효율적으로 이용하기 위해 컨테이너 환경이 주목을 받았으나 컨테이너 관리가 어렵고 복잡해서 사용하기가 어려웠습니다.
  • 쿠버네티스 등장 이후, 컨테이너 관리에 대한 복잡도가 줄어들고 비용 대비 생산성을 높일 수 있게 되어서 컨테이너 인프라 환경이 다시 주목을 받고 있습니다.
  • 쿠버네티스 환경에 스프링으로 만들어진 프로젝트를 배포하는 과정을 알아보겠습니다.
  • VirtualBox, Vagrant, kubeadm를 사용해서 로컬 환경에 쿠버네티스 환경을 구성했다고 가정을 하고 과정을 진행하겠습니다.
  • https://github.com/sysnet4admin/_Book_k8sInfra -로컬 환경에 쿠버네티스 환경을 구성
  • https://github.com/sgwon96/devopsTest - 간단한 쇼핑몰 프로젝트를 예시로 사용

Container Image 만들기

  • 쿠버네티스 환경에서 어플리케이션을 배포하는 과정
    • 컨테이너 이미지 생성
    • 컨테이너 레지스트리로 이미지 업로드
    • 쿠버네티스에서 컨테이너 이미지를 바탕으로 Deployment 생성
  • 스프링 어플리케이션을 실행시키기 위해서는 gradle wrapper를 사용해 jar 파일을 만든 후 실행시켜야 합니다.
  • 스프링 프로젝트를 Container 이미지 파일로 만들기 위해서는 다양한 방법들이 존재 합니다.

로컬 환경에서 jar 파일 빌드 , jar 파일을 이미지에 복사 후 실행

  1. jar 파일 생성
./gradlew clean build

  • 프로젝트 폴더로 들어가서 gradlew clean build를 통해 grdlew로 jar 파일 빌드
  • 프로젝트안에 있는 테스트 코드들이 실행되고 모든 테스트들을 통과하면 ./build/libs 에 실행 가능한 jar 파일이 생성
  1. Dockerfile을 통해 해당 jar 파일을 바탕으로 이미지 생성
FROM openjdk:11
ARG JAR_FILE=./build/libs/jpashop-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

  • 해당 Dockerfile을 프로젝트 폴더에 넣은 후 docker build . -t 태그 이름 명령어를 실행하면 컨테이너 이미지 생성
  • java11의 실행환경을 제공하는 openjdk:11 이미지 위에서 폴더에 있는 jar 파일을 이미지 내부로 복사 후 java 명령어를 통해 실행
  • docker images 를 통해 이미지 용량을 확인해보면 700mb를 차지

해당 빌드 방법의 문제점

  • 해당 빌드 방법은 이미 패키징 된 jar 파일을 이미지화 시켰기 때문에 약간의 소스 수정이 일어나더라도 변경된 소스로 인해 dependency들이 포함된 jar 파일 전체가 새로운 이미지로 인식 되어 전체 파일 빌드를 다시 수행하기 때문에 Docker layer의 장점을 살릴 수 없습니다.
  • 이러한 문제를 해결하기 위해 layer를 나누어 이미지를 빌드하는 방법도 있지만 Google Cloud에서 제공해주는 jib를 통해서 이미지 빌드를 최적화 해보겠습니다.

JIB를 통한 이미지 빌드

  • Jib는 Dockerfile, Docker에 의존하지 않고 Gradle, Maven에서 Jib 플러그인을 사용해 컨테이너 이미지를 빌드하는 방법
  • 어플리케이션을 종속 항목, 리소스, 클래스 등 별개의 레이어로 구성하고 Docker 이미지 레이어 캐싱을 활용해서 변경사항만 다시 빌드함으로써 빌드를 빠르게 유지
  • jib 레이어 구성과 작은 기본 이미지는 전체 이미지 크기를 작게 유지시키며 빌드 속도를 향상 시킴

Docker 빌드 흐름

Jib 빌드 흐름

Gradle에 jib 플러그인 추가하기

plugins {
	id 'org.springframework.boot' version '2.6.4'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
	id 'com.google.cloud.tools.jib' version '3.2.0'
}

jib {
	from {
		image = "eclipse-temurin:11-jre"
	}
	to {
		image = "zxcvb5434/devopstest"
		tags = ["latest"]
	}
	container {
		jvmFlags = ["-Xms128m", "-Xmx128m"]
	}
}
  • build.gradle에 jib 플러그인 설정을 추가
  • jib 3.2.0 버전 사용
  • 베이스 이미지는 java 11을 이용할 수 있는 11-jre 선택
  • image 이름, 태그 설정
./gradlew jib
  • gradlew wrapper이 있는 폴더에서 gradlew jib 명령어를 통해 이미지를 빌드한 후 자동으로 이미지 레지스트리에 푸시

  • 기존 Dockerfile을 이용한 이미지 빌드는 1분 30초가 걸린 반면에 jib를 사용한 이미지 빌드는 14초만에 완료 후 이미지 레지스트리로 업로드까지 완료

  • docker hub를 확인해보면 이미지가 성공적으로 올라갔으며 이미지 사이즈도 119MB로 기존 이미지 크기(700MB)에 비해 상당히 작아졌습니다.

쿠버네티스에 컨테이너 배포하기

디플로이먼트 생성

  • 쿠버네티스에 컨테이너화 된 어플리케이션을 배포하기 위해서는 디플로이먼트를 생성해야합니다.
  • 파드 : 쿠버네티스 클러스터에서 실행되는 최소 단위로 독립적인 공간과 사용 가능한 IP를 지니고 있음
  • 레플리카셋 : 명시된 동일 파드 개수를 항상 실행시켜주는 것을 보장 시켜주는 쿠버네티스 오브젝트
  • 디플로이먼트 : 파드와 레플리카셋에 대한 선언적인 업데이트를 제공하는 쿠버네티스 오브젝트로 오늘날에는 쿠버네티스 상에서 컨테이너를 배포할 때 디플로이먼트를 사용
  • 쿠버네티스 디플로이먼트 오브젝트를 통해서 컨테이너를 배포해보겠습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: devops-spring-deployment
spec:
  selector:
    matchLabels:
      app: devops-spring-app
  replicas: 1
  template:
    metadata:
      labels:
        app: devops-spring-app
    spec:
      containers:
        - name: core
          image: zxcvb5434/devopstest:latest
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
              protocol: TCP
          resources:
            requests:
              cpu: 500m
              memory: 1000Mi
  • 도커 허브의 zxcvb5434/devopstest 저장소에서 latest 태그를 지닌 이미지를 다운받아 pod를 생성
  • 스프링 서버가 구동되는 8080포트로 해당 컨테이너를 노출
  • kubectl apply -f deployment.yaml 명령어를 통해 디플로이먼트를 생성하고 get 명령어를 통해 deployment, pod이 성공적으로 생성되었음을 확인
kubectl apply -f deployment.yaml
kubectl get deployment
kubectl get pod

서비스 생성

  • Pod는 IP가 랜덤하게 지정이 되고 새롭게 생길때마다 변하기 때문에 외부에서 접속하기 위해서는 고정된 IP를 지니고 여러 Pod를 대상으로 로드밸런싱을 해주는 서비스가 필요합니다.
  • Service : 파드 집합에서 실행중인 어플리케이션을 고정된 IP를 지니고 여러 Pod를 연결시켜주는 쿠버네티스 오브젝트
  • 노드포트를 통해 고정 포트로 각 노드의 IP에 서비스를 노출 시킬 수 있습니다.
apiVersion: v1
kind: Service
metadata:
  name: devops-spring-service
spec:
  type: NodePort
  ports:
    - port: 80
      protocol: TCP
      targetPort: 8080
  selector:
    app: devops-spring-app
  • port : Cluster 내부에서 사용할 Service 객체의 포트
  • targetPort : Service로 전달된 요청을 Pod로 전달할 때 사용하는 포트
  • NodePort : 외부에서 접속하기 위해 사용하는 포트 - 따로 지정하지 않으면 랜덤하게 생성
  • kubectl apply 명령어를 통해 Service를 생성하고 get 명령어를 통해 조회해보겠습니다.

  • 가상머신의 30696포트가 서비스의 80포트로 연결되었고 최종적으로 pod의 8080포트로 연결 되었습니다.

  • 가상머신의 아이피 (192.168.56.10)의 30696 포트로 접속하면 배포된 어플리케이션을 확일 할 수 있습니다.
  • 192.168.56.101, 102, 103 등 모든 워커 노드에서도 접근 가능

포트포워딩을 통해 외부에서 어플리케이션에 접근하기

  • 기존에 사용하던 192.168.56.10은 가상 머신을 만들 때 할당해준 아이피이기 때문에 가상머신과 가상 머신을 작동시키는 컴퓨터에서만 접근 할 수 있습니다.
  • 포트포워딩을 통해 컴퓨터의 특정 포트와 가상머신의 특정 포트를 연결시켜 보겠습니다.

  • VirtualBox의 설정에 들어가 해당 노드의 포트포워딩을 설정
  • 가상머신이 설치되어 있는 컴퓨터의 8080 포트를 가상머신의 30696 포트와 연결

  • localhost:8080
  • 같은 공유기를 사용하고 있는 컴퓨터에서는 해당 컴퓨터의 아이피:8080으로도 접속 가능
  • 하지만 해당 아이피는 사설 아이피로 외부에서 접근이 불가능 하며 같은 공유기를 사용하고 있는 장치에서만 접근 가능
  • 사설 아이피 : 일반 가정이나 회사 내부에서 사용하는 아이피로 로컬 네트워크에 연결된 장치만 접근 가능
  • 공인 아이피 : 전 세계에서 유일한 아이피이며 ISP를 통해 공급 받음 보통 공유기에 할당 되어 있습니다.
  • 외부에서 접근 가능하게 만들어주면 공유기에 포트포워딩 설정을 통해 공인 아이피와 사설 아이피를 연결시켜야 합니다.

  • 네이버에 내 아이피 주소 확인을 통해 공유기의 공인 아이피 주소를 알 수 있음
  • 해당 아이피 주소를 브라우저에 입력해 공유기 설정으로 들어가보겠습니다.

  • 포트 포워드 설정을 통해 공인 아이피의 특정 포트와 사설 아이피의 특정 포트를 연결시켜줄 수 있습니다.

  • 공인아이피의 8080 포트와 사설 아이피(192.168.56.15)의 8080 포트를 연결

  • 내부 네트워크에 접속해 있지 않은 휴대폰으로 공인아이피 주소를 통해 접근 성공

네트워크 도식도

  • 최종적으로 로컬 클러스터에 스프링으로 만들어진 컨테이너를 배포했고 포트포워딩을 통해 외부에서 접근 가능하도록 설정

포트포워딩을 통해 외부에서 kubectl 명령 사용하기

  • kubectl를 통해 클러스터에 접근하기 위해서는 마스터노드의 6443 포트로 접근해야 합니다.
  • 마스터 노드의 6443번 포트를 위 방법과 마찬가지고 포트포워딩을 통해 컴퓨터의 사설 아이피, 공유기의 외부 아이피와 연결보겠습니다.
  • kubectl 에서 클러스터를 접근하기 위해서는 kubeconfig 파일이 필요하다.
  • cat $HOME/.kube/config 명령어를 통해 확인할 수 있습니다.
  • 하지만 기존에 만들어진 kubeconfig 파일은 내부 아이피(192.168.56.10)을 기준으로 만들어졌기 때문에 수정이 필요

Kube-api-server에 접근할 수 있는 새로운 IP 추가하기

  • 기존에 kubeadm을 통해 만든 클러스터는 내부 IP를 기준으로 만들었기 때문에 내부 아이피로의 접근만 가능합니다.
  • 포트포워딩을 통해 연결된 공인아이피로는 해당 kubeconfig 파일로 접근할 수 없습니다.
  • kubernetes 설정에 공인 아이피를 추가해주고 새롭게 인증서를 만들어야 합니다.
kubectl get configmap -n kube-system
kubectl get configmap kubeadm-config -n kube-system -o yaml

apiServer:
  certSANs:
  - 221.146.125.131
  - 192.168.56.10 # 공인아이피 추가
  extraArgs:
    authorization-mode: Node,RBAC
  timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta2
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns:
  type: CoreDNS
etcd:
  local:
    dataDir: /var/lib/etcd
imageRepository: k8s.gcr.io
kind: ClusterConfiguration
kubernetesVersion: v1.18.20
networking:
  dnsDomain: cluster.local
  podSubnet: 172.16.0.0/16
  serviceSubnet: 10.96.0.0/12
scheduler: {}
  • certSANs 항목에 포트포워딩으로 연결한 공인아이피를 추가하겠습니다.
  • /etc/kubernetes/pki 에 존재하는 기존에 만든 인증서와 키를 삭제 - apiserver.crt, key
sudo kubeadm init phase certs apiserver --config kubeadm-conf.yaml
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text

...
DNS:m-k8s, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:10.96.0.1, IP Address:10.0.2.15, IP Address:221.146.125.131, IP Address:192.168.56.10
...
  • kubeadm init phase certs apiserver 명령어로 새로운 인증서와 키 생성
  • openssl 명령어를 통해 새로운 인증서를 확인해보면 공인 아이피 주소가 추가되어 있습니다.
  • cp $HOME/.kube/config ./kubeconfig 명령어를 통해 kubeconfig 파일을 복사한 후
  • server: https://221.146.125.131:6443 을 공인아이피 주소로 변경시켜주면 외부에서 해당 kubeconfig 파일을 통해 공인아이피 주소로 해당 클러스터에 접근 가능

최종 정리

  • 쿠버네티스 환경에 스프링 어플리케이션을 배포하기 위해서는 이미지 빌드, 이미지 업로드, Deployment 생성 3가지 과정을 거쳐야 합니다.
  • 스프링 어플리케이션을 jib를 사용해서 컨테이너 이미지화 시키는 것이 Docker Layer의 이점을 살릴 수 있어서 용량이 적고 빌드 속도가 빠릅니다.
  • 쿠버네티스 환경에서는 컨테이너 이미지를 바탕으로 Deployment, Service를 만들어서 어플리케이션을 배포합니다.
  • 가상 머신에서 배포한 어플리케이션은 해당 가상 머신이 설치된 컴퓨터에서만 접근이 가능하기 때문에 포트 포워딩을 통해 공인 아이피와 연결시켜주면 외부에서 접근 가능

Reference

https://blog.dudaji.com/kubernetes/2020/04/08/add-ip-to-kube-api-cert.html
https://cloud.google.com/java/getting-started/jib?hl=ko
https://jihyeong-ji99hy99.tistory.com/161![](https://velog.velcdn.com/images%2Fsgwon1996%2Fpost%2Fe9697bf6-c5c0-403b-aa6e-21134d4a829c%2Fimage.png)

좋은 웹페이지 즐겨찾기