서론 (Introduction)

QueryDSL은 ORM을 사용하다 보면 언젠가는 사용하게 될 기술 중 하나입니다. 이를 설정하는 법을 구글링해보면 표준적인 설정 방법이 없기 때문에 각기 다른 방법을 제시하고 그만큼 설정에 에러를 많이 겪는 경우가 보입니다. 여기에서는 자바 버전에 따라 가장 간단하게 QueryDSL을 설정하는 방법을 알아보고, 각 설정의 의미에 대해서 서술해보도록 하겠습니다.

본문 (Body)


간단한 QueryDSL 원리

설정에 대해 알아보기전, 간단하게 QueryDSL의 원리에 대해 알면 설정을 이해하기 쉽습니다. 만약, 빠르게 설정만 하고 싶다면 아래 (QueryDSL 설정)를 바로 참고하시기 바랍니다.

 

QueryDSL은 "쿼리를 안전하고 쉽게 하기 위한 도메인 특화언어(DSL, Domain Specific Language)"입니다. 이를 위해선, 기존 자바코드를 바탕으로 JPA, MongoDB 같은 도메인에 맞는 언어를 생성하는 과정이 필요합니다. QueryDSL은 다음 과정을 통해 도메인 언어를 생성합니다.

 

1. compileJava 태스크를 실행합니다.

2. @Entity와 같이 쿼리와 관련된 어노테이션과 클래스를 스캔합니다.

3. 스캔한 클래스를 활용해 도메인에 맞는 언어를 생성합니다. (ex. Student.class ->  QStudent.class)

 

이제 개발자는 QueryDSL이 생성한 언어를 이용해 자바언어를 토대로 쿼리를 작성할 수 있습니다.


QueryDSL 설정

QueryDSL은 다음 코드를 build.gradle 파일에 추가해 설정할 수 있습니다.

dependencies{
    //Java EE를 사용하는 경우, (javax 패키지를 참조할 수 있는 경우)
    implementation 'com.querydsl:querydsl-jpa:5.0.0'
    annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa'
    annotationProcessor 'javax.persistence:javax.persistence-api:2.2.0'

    
    //Jakarta EE를 사용하는 경우, (jakarta 패키지를 참조할 수 있는 경우)
    implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
    annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
    annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
    
 }
 
 ...
 
 task deleteGenerated(type: Delete) {
    delete "src/main/generated"
}

clean.dependsOn deleteGenerated

 

각 설정의 이유에 대해서 설명해보겠습니다. 

 

먼저, 아래 의존성은 queryDSL에서 쿼리 기능을 구현하는데 필요한 클래스를 불러오기 위해 사용됩니다.

jakarta 의 경우 기존에 javax.persistence.* 로 선언된 패키지들이  jakarta.persistence.* 로 변경되었기 때문에, 올바른 참조를 위해 추가로 선언한 필요가 있습니다.

implementation 'com.querydsl:querydsl-jpa:5.0.0'

annotationProcessor는 Gradle에서 컴파일 타임에 어노테이션을 처리해 코드를 생성 또는 검증하는데 사용하기 위한 키워드입니다. APT는 Annotation Processing Tool의 약자입니다. 즉, querydsl에서 어노테이션 처리를 위한 기능을 가져오기 위해 사용됩니다.

이 또한, jakarta 의 경우 기존에 javax.persistence.* 로 선언된 패키지들이  jakarta.persistence.* 로 변경되었기 때문에, 올바른 참조를 위해 추가로 선언한 필요가 있습니다.

annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa'

@Entity와 같은 기본 어노테이션들은 자바에서 표준적으로 정의되어 있습니다. 따라서 각 환경에 따라 필요한 정의를 annotationProcessor를 사용해 정의할 필요가 있습니다.

 

annotationProcessor 'javax.persistence:javax.persistence-api:2.2.0'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'


QueryDSL 사용

위 설정이 올바르게 완료되면, compileJava 라는 태스크를 실행할 수 있습니다. ./gradlew compileJava 커맨드를 이용해 태스크를 실행하면, 위에서 설명한 DSL 생성과정이 진행됩니다.

2. @Entity와 같이 쿼리와 관련된 어노테이션과 클래스를 스캔합니다.
3. 스캔한 클래스를 활용해 도메인에 맞는 언어를 생성합니다. (ex. Student.class ->  QStudent.class)

그럼 이제 queryDSL 문법에 따라 설정이 가능합니다!

 

 

서론 (Introduction)

JPA를 사용하면 대표적인 구현체인 HIbernate를 사용하게 됩니다. HIbernate는 SQL 쿼리를 생성하주는 기능을 가지고 있는데, MySQL, Oracle 등 데이터베이스 마다 문법이 조금씩 다르기 때문에 Dialect(방언)을 설정해 주어야합니다. 이번 글에서는 Oracle 데이터베이스를 위한 Dialect 설정에 대해서 다루어보도록 하겠습니다.

 

본문 (Body)

a. org.hibernate.dialect

Hibernate에서는 데이터베이스 방언들을 org.hibernate.dialect 패키지에 정리하고 있습니다. 이 패키지의 내용은 hibernate 버전이 변경되면서 조금씩 바뀌어왔습니다. 여기에서는 이전 버전과 현재 버전의 dialect 적용에 대해서 알아보도록 하겠습니다.


B. 구버전의 Dialect

dialect 관련된 내용을 검색하면, 아래와 같이 데이터베이스 버전 별로 dialect를 설정하는 방법에 대한 질문이 많습니다. 답변을 보면 oracle 데이터베이스의 경우 버전 별로 Oracle10gDialect, Oracle12cDialect dialect를 설정하라고 하고 있습니다. 하지만 현재 hibernate 최근 버전(6.2.9-Final)에서는 이를 사용할 경우 에러가 발생합니다.


C. 최근 버전의 Dialect

최근 버전의 Dialect에서는 버전별로 각각의 Dialect를 사용하는 대신 통합된 Dialect를 사용하려는 경향을 보이고 있습니다. 따라서 이전에 사용된 버전별 Dialect는 Deprecated 처리되어 있는 것을 찾을 수 있습니다.

 따라서 OracleDialect를 사용하기 위해선 hibernate.dialect를 다음과 같이 설정해야합니다.

spring:
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.OracleDialect

결론 (Conclusion)

평소에 개발하듯이 모르는게 생기면 구글링을 했는데, 이번에는 예전 버전을 기준으로 작성된 답변이나 블로그 글이 많아 개발을 하는데 많이 해매게 되었습니다. 역시 가장 확실한 건 직접 코드를 확인하는 것인 것 같습니다. 다른 분들은 저처럼 잘못된 답변으로 해매지말고 이 글로 명쾌하게 hibernate.dialect 를 설정할 수 있기를 바랍니다.

 

참조(Reference)

1. https://docs.jboss.org/hibernate/orm/6.3/userguide/html_single/Hibernate_User_Guide.html#database-dialect

상황

Spring MessageSource를 사용해 messages.properties 에서 메시지를 읽는 과정에서 한글이 아래와 같이 깨지는 상황이 생겼다.

분석

문자를 인코딩하는 과정에서 발생한 에러라고 생각되어 message.properties 파일의 인코딩을 확인한 결과 ISO-8859-1 이었지만, MessageSource는 UTF-8으로 읽어 문자열 형식이 일치하지 않는 것을 확인했다.

해결


IntelliJ에서 파일 인코딩할 때, UTF-8을 사용하도록 설정하니 한글 깨짐이 해결되었다.

 

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

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

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