이전 시간에는 미니큐브에서 스프링 프로젝트와 MySQL을 실행하고, 포트포워딩을 사용하여 외부에서 접속하는 방법을 배웠습니다. 그러나 이 방법으로는 하나의 서비스만 접속할 수 있습니다. 만약 여러 개의 서비스를 운영하고 있다면, 파이썬 기반의 플라스크나 장고, 자바 기반의 스프링, 그리고 자바스크립트 기반의 노드제이에스 등을 함께 사용하는 경우 라우팅이 필요합니다. 이번 시간에는 쿠버네티스에서 라우팅할 수 있는 방법을 배우겠습니다.

domain.com/spring → spring project

domain.com/flask → flask project

1. 샘플 프로젝트 만들기

  1. 스프링의 경우, 이전 시간에 만들었던 것을 그대로 활용할 예정입니다.
  2. 파이썬 프로젝트를 만들기 싫다면, 스킵하고 아래 flask-deploment.yaml 에 명시된 샘플 이미지를 그대로 사용할 수 있습니다.
  3. 플라스크의 경우, 다음과 같이 코드를 작성합니다.
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, Flask!'

if __name__ == '__main__':
    app.run(host="0.0.0.0",port=80, debug=True)

  1. Dockerfile 작성
# Use an official Python runtime as the base image
FROM python:3.9

WORKDIR /app

COPY . .

RUN pip install Flask

EXPOSE 80
# Run the app.py file
CMD ["python", "app.py"]

  1. 빌드 (ec2를 x86으로 생성해줬으니 맞춰서 이미지를 만들어줄것입니다. )
docker build -t lee01042000/flask-test --platform linux/amd64 .

  1. 실행
docker run -it -p 8080:80 lee01042000/flask-test

  1. 크롬에서 8080 포트로 테스트해보세요. 만약 "Hello, Flask!"가 잘 나오면 끝입니다!
  2. 쿠버네틱스가 이미지를 가져오게 하기 위해 도커 허브에 푸쉬합니다. 만약 로그인이 안되어 있다면 로그인을 하고 진행하시면 됩니다.
docker push lee01042000/flask-test

2. Python Deployment & Service 작성하기

이전 포스트에서 스프링 프로젝트를 실행한 것처럼, 서비스와 배포를 명시한 python-deploy.yaml을 작성할 것입니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-deployment
  labels:
    app: flask
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask
  template:
    metadata:
      labels:
        app: flask
    spec:
      containers:
      - name: flask
        image: 'lee01042000/flask-test'
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: flask-service
spec:
  type: NodePort
  selector:
    app: flask
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80
      nodePort: 30008

kubectl apply -f python-deploy.yaml 을 이용해 쿠버네틱스에 적용하면 배포와 서비스를 실행할 수 있습니다.

minikube ip 명령어를 사용하여 IP 주소를 찾은 다음, curl {minikubeip}:30008을 실행하여 "Hello, Flask!"가 출력되면 성공입니다.

2. Ingress로 라우팅 구현하기

쿠버네틱스에서는 ingress를 사용하여 서비스 라우팅이 가능합니다.

이렇게 ingress는 요청을 먼저 받아들여 적절한 서비스로 라우팅할 수 있습니다.

URL 디렉터리를 기반으로 하거나, 요청 헤더에 포함된 호스트명을 가지고도 라우팅이 가능합니다.

L7 스위치라고 생각하시면 이해하기 쉽습니다.

2.1 Nginx ingress controller 설치

ingress 룰을 적용하기 위해선 룰을 구현하는 ingress controller가 필요합니다. 공식 홈페이지에서는 다양한 ingress controller를 찾아볼 수 있지만, 이 문서에서는 nginx를 사용해보겠습니다.

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

  1. NGINX 인그레스 컨트롤러를 활성화하기 위해 다음 명령어를 실행합니다.
  2. minikube addons enable ingress

2.2 ingress 룰 작성

참고로 nginx.ingress.kubernetes.io/rewrite-target 옵션을 설정하지 않으면 http://minikubeip/spring 으로 요청시 /spring이 url에 포함되서 포워딩이 되니 / 로 명시해야합니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /spring
        pathType: Prefix
        backend:
          service:
            name: spring-service
            port:
              number: 8080
      - path: /flask
        pathType: Prefix
        backend:
          service:
            name: flask-service
            port:
              number: 80

  1. kubectl apply -f ingress.yaml 명령어로 적용합니다.
  2. curl {minikubeip}/flask & curl {minikubeip}/spring을 쳤을 때 사이 좋게 나오면 성공입니다!

3. 외부 접속 연결하기

아직은 ec2 내부에서만 접속이 가능합니다. 즉, client → server — x → ingress → service → pod인 상태입니다.

그럼 이제 서버포트랑 미니큐브 사이만 연결해주면 됩니다.

이를 포트포워딩이라고 합니다.

가장 친숙한 방법은 nginx를 사용하는 것입니다.

3.1 EC2에 Nginx를 설치

sudo amaxon-linux-extras install nginx1

3.2 EC2에 라우팅 정책 설정

minikube ip 가 192.168.49.2 인 경우 아래와 같이 server.location을 설정합니다.

# For more information on configuration, see:
#   * Official English Documentation: <http://nginx.org/en/docs/>
#   * Official Russian Documentation: <http://nginx.org/ru/docs/>

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See <http://nginx.org/en/docs/ngx_core_module.html#include>
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }

	location / {
            proxy_pass         http://192.168.49.2/;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    }

# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl http2;
#        listen       [::]:443 ssl http2;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers PROFILE=SYSTEM;
#        ssl_prefer_server_ciphers on;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        error_page 404 /404.html;
#            location = /40x.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#            location = /50x.html {
#        }
#    }

}

3.3 재설치 및 재실행

sudo systemctl restart nginx

4. 테스트

이제 EC2서버에 접속해보겠습니다.

아래와 같이 잘 출력되면 성공입니다!

 

이 글을 보는 사람은 도커와 쿠버네티스에 대한 기본적인 지식이 있다고 생각할 것입니다.

도커는 알겠는데 쿠버네티스는 처음이라면 아래 동영상을 보고 진행하시기 바랍니다.

https://www.youtube.com/watch?v=s_o8dwzRlu4&ab_channel=TechWorldwithNana

쿠버네티스 컨테이너에 외부에서 접속하는 과정은 일반적으로 다음과 같습니다.

외부 → nginx → minikube → ingress controller → service → pod

각각이 잘 동작하는지 확인하려면 거꾸로 구현해야 합니다.

여기서는 서비스와 파드를 구현해보고 외부에서 접속하는 과정은 ingress 대신 포트포워딩을 이용해 간단히 구현해볼 것입니다.

쿠버네티스에 스프링 프로젝트 배포하기

1. EC2에 Minicube 설치하기

1.1. EC2 생성하기

  • ARM 버전으로 진행하면 이슈가 많아서 x86으로 진행하는 것을 추천합니다.
  • 인스턴스는 미니큐브 조건 때문에 t2 미디엄을 사용합니다.
    • 프리티어 사용하면 미니큐브 설치 에러가 발생합니다.
  • SSH로 접속합니다.
  • 프리티어가 아니니까 주의하시기 바랍니다.

1.2. 도커 설치하기

  • 두 단계로 나누어서 설치할 것입니다.
  • docker info를 입력해서 잘 나오면 설치가 잘 된 것입니다.

1.3. 미니큐브 설치하기

  • 공식 홈페이지에서 환경에 맞춰서 설치할 것입니다.
  • minikube version을 입력해서 잘 나오면 설치가 잘 된 것입니다.

1.4. kubectl 설치하기

  • 환경에 맞춰서 설치할 것입니다.
  • kubectl version --short를 입력해서 잘 나오면 설치가 잘 된 것입니다.

2. 샘플 프로젝트 만들기

  • 테스트를 위해서는 Spring과 MySQL을 사용하는 샘플 프로젝트를 만들어야 합니다.
  • 이 부분을 뛰어넘고 싶은 분은 그냥 3번부터 시작하시면 됩니다. 제가 미리 만들어둔 프로젝트를 사용할 것입니다.
  • 참고로 제 프로젝트는 /으로 GET 요청을 받으면 HelloEntity라는 인스턴스를 만들어서 DB에 저장하고 결과를 반환해주는 단순한 것입니다.

3. 샘플 프로젝트 배포하기

3.1. ConfigMap 작성

https://kubernetes.io/docs/concepts/configuration/configmap/

vim config.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  # property-like keys; each key maps to a simple value
  mysql-url : mysql-service

  • 환경 변수에 세팅해줄 URL 주소를 입력합니다.
  • 아래 커맨드를 입력하고 잘 되면 된 것입니다.
kubectl apply -f config.yaml

  • kubectl describe configMap mysql-config을 입력하면 들어가 있는 값이 나옵니다.
[ec2-user@ip-172-31-42-29 spring-test]$ kubectl describe configMap mysql-config
Name:         mysql-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
mysql-url:
----
mysql-service

BinaryData
====

Events:  <none>

3.2. Secret 작성

  • 아이디와 비밀번호 등 민감한 정보는 config에 그대로 노출되면 안 됩니다.
    • 파일에 그대로 저장하면 위험합니다.
  • Secret은 암호화된 상태로 저장하기 때문에 털려도 그나마 괜찮습니다.
  • 그러나 설정하지 않으면 기본적으로 암호화 상태로 저장되지 않습니다.
  • 그래서 쿠버네티스에 배포할 수 있는 권한을 가진 사람이 간접적으로 접근 가능합니다.
  • 암호화하려면 따로 설정이 필요합니다.
  • 하지만 우리는 테스트니까 하지 않을 것입니다.

https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret

  • 아래 커맨드를 사용하시기 바랍니다.
echo -n mysql-user | base64 # bXlzcWwtdXNlcg==
  • stringData 옵션을 사용하면 data 옵션을 대체할 수 있습니다. 하지만 특수문자를 포함한 문자열은 사용할 수 없습니다.
  • 저희는 stringData 옵션을 사용할 예정입니다.
  • 아래 커맨드를 입력하여 mysql-secret.yaml 파일을 적용합니다.
apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  password: mysql-password

  • kubectl describe secret secret-basic-auth 를 입력하면 아래와 같은 결과가 나타납니다.
Name:         secret-basic-auth
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  kubernetes.io/basic-auth

Data
====
password:  14 bytes

3.3 MySQL Deployment & Service 작성하기

https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ 에서 예시를 확인할 수 있습니다.

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx-deployment

  labels:

    app: nginx

spec:

  replicas: 3

  selector:

    matchLabels:

      app: nginx

  template:

    metadata:

      labels:

        app: nginx

    spec:

      containers:

      - name: nginx

        image: nginx:1.14.2

        ports:

        - containerPort: 80

아래와 같이 수정해봅시다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-deploy
  labels:
    app: mysql
    tier: database
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: 'mysql:8.0.26'
          ports:
            - containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: secret-basic-auth
                  key: password
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  selector:
    app: mysql
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

이후 kubectl apply -f mysql-deployment.yaml 를 입력합니다.

kubectl get all 로 파드, 서비스, 배포 그리고 레플리카셋을 확인할 수 있습니다.

아래와 같이 파드가 잘 동작하면 성공입니다.

NAME                                READY   STATUS    RESTARTS   AGE
pod/mysql-deploy-847cd594ff-8hkkc   1/1     Running   0          5s

로그를 자세히 확인하려면 아래의 커맨드를 입력합니다.

kubectl logs -f pod/mysql-deploy-847cd594ff-8hkkc

아래와 같은 로그가 나타나면 준비가 완료된 상태입니다.

2023-02-22T04:59:55.553138Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.26'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.

배시 쉘에서 직접 접속하려면 아래의 커맨드를 입력합니다.

kubectl exec -it pod/mysql-deploy-847cd594ff-8hkkc bash

패스워드는 아까 시크릿에서 명시한 mysql-password 입니다. exit 두 번 입력하여 콘솔로 나옵니다.

3.4 Spring 배포와 Service 작성하기

MySQL 배포와 형식이 비슷하기 때문에, 일단 아래와 같이 복사합니다.

cp mysql-deployment.yaml spring-deployment.yaml

그리고 수정을 좀 해줄 겁니다. 저희가 만든 테스트 프로젝트는 환경 변수가 3개이고, 사용자는 그냥 root를 넣고, 비밀번호는 시크릿, URL은 config를 참조할 겁니다.

  • MYSQL_USER
  • MYSQL_PWD
  • MYSQL_URL
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-demo-deploy # 이름을 바꿔줍니다.
  labels:
    app: spring-demo # 레이블을 바꿔줍니다.
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-demo # 당연히 바꿔줍니다.
  template:
    metadata:
      labels:
        app: spring-demo # 당연히 바꿔줍니다.
    spec:
      containers:
        - name: spring-demo # 당연히 바꿔줍니다.
          image: 'lee01042000/k8s-auto-deployment-test:main-2023-02-21T11-36-33'
          ports:
            - containerPort: 8080 # Spring은 8080 포트를 사용하므로 바꿔줍니다.
          env:
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PWD
              valueFrom:
                secretKeyRef:
                  name: secret-basic-auth
                  key: password
            - name: MYSQL_URL
              valueFrom:
                configMapKeyRef:
                  name: mysql-config
                  key: mysql-url

서비스도 추가해 줄 겁니다. 나중에 외부에서 이 파드에 접속하려면 서비스가 있어야 합니다.

apiVersion: v1
kind: Service
metadata:
  name: spring-service
spec:
  selector:
    app: spring-demo
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080

정갈한 버전은 아래와 같습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-demo-deploy
  labels:
    app: spring-demo
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-demo
  template:
    metadata:
      labels:
        app: spring-demo
    spec:
      containers:
        - name: spring-demo
          image: 'lee01042000/k8s-auto-deployment-test:main-2023-02-21T11-36-33'
          ports:
            - containerPort: 8080
          env:
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PWD
              valueFrom:
                secretKeyRef:
                  name: secret-basic-auth
                  key: password
            - name: MYSQL_URL
              valueFrom:
                configMapKeyRef:
                  name: mysql-config
                  key: mysql-url
---
apiVersion: v1
kind: Service
metadata:
  name: spring-service
spec:
  selector:
    app: spring-demo
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080

kubectl get all을 실행해 보세요. 아래와 같이 잘 나오면 성공입니다.

NAME                                     READY   STATUS    RESTARTS   AGE
pod/mysql-deploy-847cd594ff-8hkkc        1/1     Running   0          52m
pod/spring-demo-deploy-c9798b78d-bs5c7   1/1     Running   0          4s

로그를 찍어 보세요.

kubectl logs -f pod/spring-demo-deploy-c9798b78d-bs5c7

로그가 잘 찍히면 실행된 것입니다. 굿굿!

3. 요청해보기

모든 파드와 서비스는 미니큐브 안에 있으므로, 현재는 ec2 로컬에서 요청할 수 없습니다. 스프링 서비스를 미니큐브 바깥으로 연결해야 합니다. 이를 위해서는 로드밸런서나 노드포트를 이용해서 구현할 수 있습니다. 이전에 작성한 서비스를 수정해 봅시다.

apiVersion: v1
kind: Service
metadata:
  name: spring-service
spec:
  type: NodePort #스펙에 노드포트 추가
  selector:
    app: spring-demo
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 30007 # 미니큐브 바깥으로 연결할 포트번호 미니큐프ip:30007로 서비스에 연결가능

기존 파일을 수정한 코드는 아래와 같습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-demo-deploy
  labels:
    app: spring-demo
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-demo
  template:
    metadata:
      labels:
        app: spring-demo
    spec:
      containers:
        - name: spring-demo
          image: 'lee01042000/k8s-auto-deployment-test:main-2023-02-21T11-36-33'
          ports:
            - containerPort: 8080
          env:
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PWD
              valueFrom:
                secretKeyRef:
                  name: secret-basic-auth
                  key: password
            - name: MYSQL_URL
              valueFrom:
                configMapKeyRef:
                  name: mysql-config
                  key: mysql-url
---
apiVersion: v1
kind: Service
metadata:
  name: spring-service
spec:
  type: NodePort
  selector:
    app: spring-demo
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 30007

명령어를 실행한 결과를 확인해 봅시다.

kubectl get svc

위 명령어를 실행한 결과가 아래와 같으면 성공입니다.

NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes       ClusterIP   10.96.0.1       <none>        443/TCP          103m
mysql-service    ClusterIP   10.110.69.196   <none>        3306/TCP         7m19s
spring-service   NodePort    10.104.52.108   <none>        8080:30007/TCP   5m37s

이제 콘솔에서 minikube ip를 입력하면, 미니큐브에 할당된 로컬 아이피가 나옵니다. curl 192.168.49.2:30007을 입력하면, 아래와 같이 응답을 받을 수 있습니다.

HelloEntity(id=0, name=Hello)

데이터베이스에 데이터가 잘 저장되어 있는지 확인해 봅시다. MySQL에 접속하고 데이터베이스들을 확인합니다.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| k8s_test           |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

데이터베이스가 생성되어 있습니다. use k8s_test;로 데이터베이스를 선택하고, show tables;로 테이블을 확인합니다.

mysql> show tables;
+--------------------+
| Tables_in_k8s_test |
+--------------------+
| hello_entity       |
+--------------------+
1 row in set (0.00 sec)

테이블도 잘 생성되어 있습니다. select * from hello_entity;를 실행하면, 데이터가 잘 저장되어 있는 것을 확인할 수 있습니다.

mysql> select * from hello_entity;
+----+-------+
| id | name  |
+----+-------+
|  0 | Hello |
+----+-------+
1 row in set (0.00 sec)

4. 외부와 연결하기

이제 minikube ip 를 외부에서 들어오는 요청과 매핑해야 합니다.

포트포워딩 방법은 여러 가지가 있습니다. kubectl에서는 다음과 같은 명령어를 사용하여 포트포워딩이 가능합니다.

kubectl port-forward --address 0.0.0.0 service/spring-service 8080:8080

위와 같이 포워딩이 수행되면 다음과 같은 메시지가 출력됩니다.

Forwarding from 0.0.0.0:8080 -> 8080
Handling connection for 8080
Handling connection for 8080

이제 웹 페이지에서 EC2 인스턴스로 요청하면 결과를 잘 받을 수 있습니다.

쿠버네틱스 외부 연결

이 글을 보는 사람은 도커와 쿠버네티스에 대한 기본적인 지식이 있다고 생각할 것입니다.

도커는 알겠는데 쿠버네티스는 처음이라면 아래 동영상을 보고 진행하시기 바랍니다.

https://www.youtube.com/watch?v=s_o8dwzRlu4&ab_channel=TechWorldwithNana

쿠버네티스 컨테이너에 외부에서 접속하는 과정은 다음과 같습니다.

외부 → nginx → minikube → ingress controller → service → pod

각각이 잘 동작하는지 확인하려면 거꾸로 구현해야 합니다.

여기서는 서비스와 파드를 구현해 볼 것입니다.

쿠버네티스에 스프링 프로젝트 배포하기

1. EC2에 Minicube 설치하기

1.1. EC2 생성하기

  • ARM 버전으로 진행하면 이슈가 많아서 x86으로 진행하는 것을 추천합니다.
  • 인스턴스는 미니큐브 조건 때문에 t2 미디엄을 사용합니다.
    • 프리티어 사용하면 미니큐브 설치 에러가 발생합니다.
  • SSH로 접속합니다.
  • 프리티어가 아니니까 주의하시기 바랍니다.
    • 제가 회사 계정이라서 괜찮았습니다.

1.2. 도커 설치하기

  • 두 단계로 나누어서 설치할 것입니다.
  • docker info를 입력해서 잘 나오면 설치가 잘 된 것입니다.

1.3. 미니큐브 설치하기

  • 공식 홈페이지에서 환경에 맞춰서 설치할 것입니다.
  • minikube version을 입력해서 잘 나오면 설치가 잘 된 것입니다.

1.4. kubectl 설치하기

  • 환경에 맞춰서 설치할 것입니다.
  • kubectl version --short를 입력해서 잘 나오면 설치가 잘 된 것입니다.

2. 샘플 프로젝트 만들기

  • MySQL과 연결되는 샘플 프로젝트를 만들 것입니다.
  • 이 부분을 뛰어넘고 싶은 분은 그냥 3번부터 시작하시면 됩니다. 제가 미리 만들어둔 프로젝트를 사용할 것입니다.
  • 참고로 제 프로젝트는 /으로 GET 요청을 받으면 HelloEntity라는 인스턴스를 만들어서 DB에 저장하고 결과를 반환해주는 단순한 것입니다.
  • 코드 블라블라

3. 샘플 프로젝트 배포하기

3.1. ConfigMap 작성

https://kubernetes.io/docs/concepts/configuration/configmap/

vim config.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  # property-like keys; each key maps to a simple value
  mysql-url : mysql-service

  • 환경 변수에 세팅해줄 URL 주소를 입력합니다.
  • 아래 커맨드를 입력하고 잘 되면 된 것입니다.
kubectl apply -f config.yaml

  • kubectl describe configMap mysql-config을 입력하면 들어가 있는 값이 나옵니다.
[ec2-user@ip-172-31-42-29 spring-test]$ kubectl describe configMap mysql-config
Name:         mysql-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
mysql-url:
----
mysql-service

BinaryData
====

Events:  <none>

3.2. Secret 작성

  • 아이디와 비밀번호 등 민감한 정보는 config에 그대로 노출되면 안 됩니다.
    • 파일에 그대로 저장하면 위험합니다.
  • Secret은 암호화된 상태로 저장하기 때문에 털려도 그나마 괜찮습니다.
  • 그러나 설정하지 않으면 암호화 상태로 저장되지 않습니다.
  • 그래서 쿠버네티스에 배포할 수 있는 권한을 가진 사람이 간접적으로 접근 가능합니다.
  • 암호화하려면 따로 설정이 필요합니다.
  • 하지만 우리는 테스트니까 하지 않을 것입니다.

https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret

  • 아래 커맨드를 사용하시기 바랍니다.
echo -n mysql-user | base64 # bXlzcWwtdXNlcg==
  • stringData 옵션을 사용하면 data 옵션을 대체할 수 있습니다. 하지만 특수문자를 포함한 문자열은 사용할 수 없습니다.
  • 저희는 stringData 옵션을 사용할 예정입니다.
  • 아래 커맨드를 입력하여 mysql-secret.yaml 파일을 적용합니다.
apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  password: mysql-password

  • kubectl describe secret secret-basic-auth 를 입력하면 아래와 같은 결과가 나타납니다.
Name:         secret-basic-auth
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  kubernetes.io/basic-auth

Data
====
password:  14 bytes

3.3 MySQL Deployment & Service 작성하기

https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ 에서 예시를 확인할 수 있습니다.

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx-deployment

  labels:

    app: nginx

spec:

  replicas: 3

  selector:

    matchLabels:

      app: nginx

  template:

    metadata:

      labels:

        app: nginx

    spec:

      containers:

      - name: nginx

        image: nginx:1.14.2

        ports:

        - containerPort: 80

아래와 같이 수정해봅시다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-deploy
  labels:
    app: mysql
    tier: database
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: 'mysql:8.0.26'
          ports:
            - containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: secret-basic-auth
                  key: password
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  selector:
    app: mysql
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

이후 kubectl apply -f mysql-deployment.yaml 를 입력합니다.

kubectl get all 로 파드, 서비스, 배포 그리고 레플리카셋을 확인할 수 있습니다.

아래와 같이 파드가 잘 동작하면 성공입니다.

NAME                                READY   STATUS    RESTARTS   AGE
pod/mysql-deploy-847cd594ff-8hkkc   1/1     Running   0          5s

로그를 자세히 확인하려면 아래의 커맨드를 입력합니다.

kubectl logs -f pod/mysql-deploy-847cd594ff-8hkkc

아래와 같은 로그가 나타나면 준비가 완료된 상태입니다.

2023-02-22T04:59:55.553138Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.26'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.

배시 쉘에서 직접 접속하려면 아래의 커맨드를 입력합니다.

kubectl exec -it pod/mysql-deploy-847cd594ff-8hkkc bash

패스워드는 아까 시크릿에서 명시한 mysql-password 입니다. exit 두 번 입력하여 콘솔로 나옵니다.

3.4 Spring 배포와 Service 작성하기

MySQL 배포와 형식이 비슷하기 때문에, 일단 아래와 같이 복사합니다.

cp mysql-deployment.yaml spring-deployment.yaml

그리고 수정을 좀 해줄 겁니다. 저희가 만든 테스트 프로젝트는 환경 변수가 3개이고, 사용자는 그냥 root를 넣고, 비밀번호는 시크릿, URL은 config를 참조할 겁니다.

  • MYSQL_USER
  • MYSQL_PWD
  • MYSQL_URL
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-demo-deploy # 이름을 바꿔줍니다.
  labels:
    app: spring-demo # 레이블을 바꿔줍니다.
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-demo # 당연히 바꿔줍니다.
  template:
    metadata:
      labels:
        app: spring-demo # 당연히 바꿔줍니다.
    spec:
      containers:
        - name: spring-demo # 당연히 바꿔줍니다.
          image: 'lee01042000/k8s-auto-deployment-test:main-2023-02-21T11-36-33'
          ports:
            - containerPort: 8080 # Spring은 8080 포트를 사용하므로 바꿔줍니다.
          env:
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PWD
              valueFrom:
                secretKeyRef:
                  name: secret-basic-auth
                  key: password
            - name: MYSQL_URL
              valueFrom:
                configMapKeyRef:
                  name: mysql-config
                  key: mysql-url

서비스도 추가해 줄 겁니다. 나중에 외부에서 이 파드에 접속하려면 서비스가 있어야 합니다.

apiVersion: v1
kind: Service
metadata:
  name: spring-service
spec:
  selector:
    app: spring-demo
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080

정갈한 버전은 아래와 같습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-demo-deploy
  labels:
    app: spring-demo
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-demo
  template:
    metadata:
      labels:
        app: spring-demo
    spec:
      containers:
        - name: spring-demo
          image: 'lee01042000/k8s-auto-deployment-test:main-2023-02-21T11-36-33'
          ports:
            - containerPort: 8080
          env:
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PWD
              valueFrom:
                secretKeyRef:
                  name: secret-basic-auth
                  key: password
            - name: MYSQL_URL
              valueFrom:
                configMapKeyRef:
                  name: mysql-config
                  key: mysql-url
---
apiVersion: v1
kind: Service
metadata:
  name: spring-service
spec:
  selector:
    app: spring-demo
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080

kubectl get all을 실행해 보세요. 아래와 같이 잘 나오면 성공입니다.

NAME                                     READY   STATUS    RESTARTS   AGE
pod/mysql-deploy-847cd594ff-8hkkc        1/1     Running   0          52m
pod/spring-demo-deploy-c9798b78d-bs5c7   1/1     Running   0          4s

로그를 찍어 보세요.

kubectl logs -f pod/spring-demo-deploy-c9798b78d-bs5c7

로그가 잘 찍히면 실행된 것입니다. 굿굿!

3. 요청해보기

모든 파드와 서비스는 미니큐브 안에 있으므로, 현재는 ec2 로컬에서 요청할 수 없습니다. 스프링 서비스를 미니큐브 바깥으로 연결해야 합니다. 이를 위해서는 로드밸런서나 노드포트를 이용해서 구현할 수 있습니다. 이전에 작성한 서비스를 수정해 봅시다.

apiVersion: v1
kind: Service
metadata:
  name: spring-service
spec:
  type: NodePort #스펙에 노드포트 추가
  selector:
    app: spring-demo
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 30007 # 미니큐브 바깥으로 연결할 포트번호 미니큐프ip:30007로 서비스에 연결가능

기존 파일을 수정한 코드는 아래와 같습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-demo-deploy
  labels:
    app: spring-demo
    tier: backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: spring-demo
  template:
    metadata:
      labels:
        app: spring-demo
    spec:
      containers:
        - name: spring-demo
          image: 'lee01042000/k8s-auto-deployment-test:main-2023-02-21T11-36-33'
          ports:
            - containerPort: 8080
          env:
            - name: MYSQL_USER
              value: root
            - name: MYSQL_PWD
              valueFrom:
                secretKeyRef:
                  name: secret-basic-auth
                  key: password
            - name: MYSQL_URL
              valueFrom:
                configMapKeyRef:
                  name: mysql-config
                  key: mysql-url
---
apiVersion: v1
kind: Service
metadata:
  name: spring-service
spec:
  type: NodePort
  selector:
    app: spring-demo
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 30007

명령어를 실행한 결과를 확인해 봅시다.

kubectl get svc

위 명령어를 실행한 결과가 아래와 같으면 성공입니다.

NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes       ClusterIP   10.96.0.1       <none>        443/TCP          103m
mysql-service    ClusterIP   10.110.69.196   <none>        3306/TCP         7m19s
spring-service   NodePort    10.104.52.108   <none>        8080:30007/TCP   5m37s

이제 콘솔에서 minikube ip를 입력하면, 미니큐브에 할당된 로컬 아이피가 나옵니다. curl 192.168.49.2:30007을 입력하면, 아래와 같이 응답을 받을 수 있습니다.

HelloEntity(id=0, name=Hello)

데이터베이스에 데이터가 잘 저장되어 있는지 확인해 봅시다. MySQL에 접속하고 데이터베이스들을 확인합니다.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| k8s_test           |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

데이터베이스가 생성되어 있습니다. use k8s_test;로 데이터베이스를 선택하고, show tables;로 테이블을 확인합니다.

mysql> show tables;
+--------------------+
| Tables_in_k8s_test |
+--------------------+
| hello_entity       |
+--------------------+
1 row in set (0.00 sec)

테이블도 잘 생성되어 있습니다. select * from hello_entity;를 실행하면, 데이터가 잘 저장되어 있는 것을 확인할 수 있습니다.

mysql> select * from hello_entity;
+----+-------+
| id | name  |
+----+-------+
|  0 | Hello |
+----+-------+
1 row in set (0.00 sec)

4. 외부와 연결하기

이제 minikube ip 를 외부에서 들어오는 요청과 매핑해야 합니다.

포트포워딩 방법은 여러 가지가 있습니다. kubectl에서는 다음과 같은 명령어를 사용하여 포트포워딩이 가능합니다.

kubectl port-forward --address 0.0.0.0 service/spring-service 8080:8080

위와 같이 포워딩이 수행되면 다음과 같은 메시지가 출력됩니다.

Forwarding from 0.0.0.0:8080 -> 8080
Handling connection for 8080
Handling connection for 8080

이제 웹 페이지에서 EC2 인스턴스로 요청하면 결과를 잘 받을 수 있습니다.

+ Recent posts