Profile picture

[k8s] k8s Cluster 환경에서 WAS(Tomcat) - DB 연결하기

JaehyoJJAng2023년 04월 23일

▶︎ 개요

K8s 클러스터 환경에서 WAS(Tomcat)와 DB(MySQL)를 연결하는 실습을 진행해보자.


▶︎ 사전 준비

  • Docker
  • CNI(Container Network Interface)가 설치된 K8s Cluster
  • Docker Hub 계정

▶︎ DB 준비

▶︎ namespace 생성

논리적 격리를 위해 namespace를 생성해주자.

$ kubectl create namespace <namespace 이름>
$ kubectl create namespace was-prac

▶︎ Deployment 생성

위에서 생성한 namespace로 Deployment를 생성해주자. 컨테이너 이미지는 mysql:latest 이미지를 사용하였다.

{% include codeHeader.html name="mysql-deploy.yaml" %}

apiVersion: apps/v1
kind: Deployment
metadata:
  name: was-mysql
  namespace: was-prac
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: docker.io/mysql
          ports:
            - containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: "secret"

mysql의 password는 "secret"으로 지정하였음.


pod가 정상적으로 생성되었는지 확인

$ kubectl -n was-prac get pods -w
NAME                         READY   STATUS    RESTARTS   AGE
was-mysql-55f84c9c8b-hnq4l   1/1     Running   0          2m15s

Pod 로그 조회

$ kubectl logs -n was-prac pod/was-mysql-55f84c9c8b-hnq4l

‣ Service 생성

cluster 내에서만 통신할 수 있도록 Service 객체를 ClusterIP Type으로 생성하고 Deployment를 여기로 노출시키도록 함.

{% include codeHeader.html name="mysql-svc.yaml" %}

apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: was-prac
spec:
  ports:
    - name: mysql-svc
      port: 3306
      targetPort: 3306
  selector:
    app: mysql
  type: ClusterIP # 굳이 이렇게 지정안해줘도 Default가 ClusterIP 이다.

생성된 Service 확인

$ kubectl -n was-prac get svc
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
mysql-svc   ClusterIP   10.100.78.196   <none>        3306/TCP   2m31s

‣ 예제용 DB 생성

Pod 이름 확인

$ kubectl -n was-prac get pods | grep 'mysql'
was-mysql-55f84c9c8b-hnq4l   1/1     Running   0          7m9s

해당 Pod 접속

$ kubectl exec -n was-prac -it pod/was-mysql-55f84c9c8b-hnq4l -- /bin/bash

위 명령어 입력 후에는 아래와 같은식으로 접속이 된다.
image


MySQL 접속

$ mysql -uroot -p
bash-4.4# mysql -uroot -p
Enter password:

접속하면 아래와 같은 화면을 볼 수 있을 것이다.
image


MySQL에 data 삽입

다음과 같은 SQL문들을 실행해주자. database, table, row들을 추가하는 SQL문이다.

CREATE DATABASE testDB;
use testDB;
CREATE TABLE testTBL (
    id INT(11) NOT NULL AUTO_INCREMENT,
    name VARCHAR(30) NOT NULL,
    constraint testTBL_pk PRIMARY KEY(id)
);
insert into testTBL(name) values("hello");
insert into testTBL(name) values("bye");
insert into testTBL(name) values("hi");
insert into testTBL(name) values("Jaehyo");
insert into testTBL(name) values("Jimmy");
// 데이터 잘 들어갔는지 테스트
SELECT * FROM testTBL;
+----+--------+
| id | name   |
+----+--------+
|  1 | hello  |
|  2 | bye    |
|  3 | hi     |
|  4 | Jaehyo |
|  5 | Jimmy  |
+----+--------+
5 rows in set (0.00 sec)

▶︎ WAS 준비

‣ JDBC driver 설치

Tomcat 공식 이미지에는 JDBC driver가 없기 때문에 필요한 SQL에 따른 driver를 jar 형태로 image의 file system에 포함시켜줘야 한다.

아래 url에서 jar를 설치해주도록 하자.
https://dev.mysql.com/downloads/connector/j/

# tar.gz로 다운 받고 아래처럼 압축 해제
$ gzip -d ./mysql-connector-j-8.1.0.tar.gz
$ tar -xvf ./mysql-connector-j-8.1.0.tar
$ mv ./mysql-connector-j-8.1.0/mysql-connector-j-8.1.0.jar ./mysql-connector.jar

‣ Sample.war 설치

apache tomcat에서 공식으로 제공하는 Sample Application인 Sample.war를 받아주자. tomcat 이미지의 file system으로 넣어주면 알아서 압축을 해제해 실행해준다.
https://tomcat.apache.org/tomcat-8.5-doc/appdev/sample/

$ curl -O https://tomcat.apache.org/tomcat-8.5-doc/appdev/sample/sample.war

‣ Dockerhub 접속

1. 도커 허브 로그인


2. 하기와 같이 새로운 repository 생성하기. 이름을 repository 이름은 was로 설정
image


‣ 톰캣 이미지 빌드

아래와 같이 Dockerfile 작성

{% include codeHeader.html name="Dockerfile" %}

FROM openjdk:10-jdk
LABEL Description="k8s WAS Image"

# 환경 변수 및 작업 경로
ENV CATALINA_HOME /usr/local/tomcat
ENV PATH ${CATALINA_HOME}/bin:${PATH}
RUN mkdir -p "${CATALINA_HOME}"
WORKDIR ${CATALINA_HOME}

# 톰캣 설치 파일 다운로드 실행 및 압축해제
RUN wget https://archive.apache.org/dist/tomcat/tomcat-10/v10.0.20/bin/apache-tomcat-10.0.20.tar.gz
RUN tar -xf apache-tomcat-10.0.20.tar.gz  --strip-components=1

# war 파일 복사
COPY ./sample.war "${CATALINA_HOME}/webapps"

# jdbc driver jar 복사
COPY ./mysql-connector.jar /usr/local/openjdk-10/lib
COPY ./mysql-connector.jar /usr/local/tomcat/lib/

# 컨테이너 포트 지정
EXPOSE 8080

# 실행
CMD ["catalina.sh", "run"]

작성한 후 다음과 같은 명령어로 image를 build 하자. (나는 맥북(ARM) 환경에서 이미지를 빌드하였으므로, amd 기반 아키텍처에서 해당 이미지가 실행될 수 있도록 amd 플랫폼으로 이미지를 빌드)

# 도커허브 로그인
$ echo "${DOCKERHUB_PASSWORD}" | docker login -u "${DOCKERHUB_USER}" --password-stdin

# amd64 플랫폼으로 이미지 빌드
$ docker buildx build --platform linux/amd64 --load --tag Unknown/was .

‣ 톰캣 이미지 배포

local 환경에서 build 명령어로 image를 생성했으니 원격 repo(Unknown/was:latest)로 push를 해줘야한다.

$ docker push [docker ID]/[repo name]:[tag명]
$ docker push Unknown/was:latest
The push refers to repository [docker.io/Unknown/was]
6f81d659225e: Pushed
79ad8cec477f: Pushed
15ed660e65d2: Pushed
2de5ed11aa68: Pushed
85d374e8f95e: Pushed
5f70bf18a086: Pushed
a6a97e3a2ea9: Pushed
d1cace45ead1: Pushed
a063f5436873: Pushed
c9a8a600ae18: Pushed
eec9922d69ca: Pushed
c594d901dc9d: Pushed
b250144690ff: Pushed
d31e0e554b0a: Pushed
7034f4f565c8: Pushed
latest: digest: sha256:dfc5cc5d98309020df9a0bc88850d146381497fe10cbc2dd5c0ec86e2ad73631 size: 3467

push 후 docker hub에 정상적으로 업로드 되었는지 확인해보자.
image


‣ Secret 생성

db의 credential 정보는 Secret으로 관리하는 것이 좋다. Secret을 생성해 Tomcat Deployment에 지정해주면 된다.

{% include codeHeader.html name="tomcat-secret.yaml" %}

apiVersion: v1
kind: Secret
metadata:
  name: tomcat-secret
  namespace: was-prac
stringData:
  DB_URL: "jdbc:mysql://mysql.was-prac.svc.cluster.local:3306/testDB"
  DB_USER: "root"
  DB_PASSWORD: "secret"

‣ Deployment 생성

docker hub의 원격 repo로 업로드된 image(Unknown/was:latest)를 Tomcat Deployment의 베이스 이미지로 지정해주면 된다.

{% include codeHeader.html name="tomcat-deploy.yaml" %}

apiVersion: apps/v1
kind: Deployment
metadata:
  name: was-tomcat
  namespace: was-prac
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tomcat
  template:
    metadata:
      labels:
        app: tomcat
    spec:
      containers:
        - name: tomcat
          image: docker.io/yshrim12/was:latest
          resources:
            limits:
              cpu: "0.5"
              memory: 1Gi
          ports:
            - containerPort: 8080
          startupProbe:
            httpGet:
              path: "/"
              port: 8080
            periodSeconds: 5
            failureThreshold: 1
          readinessProbe:
            httpGet:
              path: "/"
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: "/"
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          env:
            - name: db_url
              valueFrom:
                secretKeyRef:
                  name: tomcat-secret
                  key: DB_URL
            - name: db_user
              valueFrom:
                secretKeyRef:
                  name: tomcat-secret
                  key: DB_USER
            - name: db_password
              valueFrom:
                secretKeyRef:
                  name: tomcat-secret
                  key: DB_PASSWORD

파드가 정상적으로 생성되었는지 확인

$ kubectl -n was-prac get pods | grep 'tomcat'

‣ Service 생성

{% include codeHeader.html name="tomcat-svc.yaml" %}

apiVersion: v1
kind: Service
metadata:
  name: tomcat-svc
  namespace: was-prac
spec:
  ports:
    - port: 8080
      targetPort: 8080
      nodePort: 30002
  selector:
    app: tomcat
  type: NodePort

서비스가 정상적으로 생성되었는지 확인

$ kubectl -n was-prac get svc | grep 'tomcat'
tomcat-svc   NodePort    10.99.190.154   <none>        8080:30002/TCP   44s

워커 노드 INTERNAL-IP 조회

$ kubectl get nodes -o wide
NAME         STATUS   ROLES           AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE                           KERNEL-VERSION                 CONTAINER-RUNTIME
k8s-master   Ready    control-plane   14h   v1.27.1   192.168.56.30    <none>        Rocky Linux 8.8 (Green Obsidian)   4.18.0-477.10.1.el8_8.x86_64   containerd://1.6.21
k8s-node1    Ready    <none>          14h   v1.27.1   192.168.56.101   <none>        Rocky Linux 8.8 (Green Obsidian)   4.18.0-477.10.1.el8_8.x86_64   containerd://1.6.21
k8s-node2    Ready    <none>          12h   v1.27.1   192.168.56.102   <none>        Rocky Linux 8.8 (Green Obsidian)   4.18.0-477.10.1.el8_8.x86_64   containerd://1.6.21

‣ 외부 노출 테스트

크롬창 또는 터미널 창에서 http://[Cluster가 구성된 NODE의 IP]:[NodePort로 노출된 tomcat Service의 Port 번호]로 접속 또는 curl 명령을 날려보자.

NodePort는 tomcat-svc.yaml에서 30002번으로 지정했으니 url은 예시로 http://192.168.56.[101,102]:30002가 된다.

[101,102]는 노드1번, 노드2번의 INTERNAL-IP 주소이다.

$ curl -s -X GET 192.168.56.101:30002 | grep 'Apache Tomcat'
        <title>Apache Tomcat/10.0.20</title>
                <h1>Apache Tomcat/10.0.20</h1>

다음처럼 tomcat이 정상적으로 배포된 것을 확인할 수 있었다.


▶︎ WAS -> DB 접속 테스트

DB(MySQL)와의 연결이 제대로 이루어졌는지 확인하기 위해 간단한 테스트 코드를 작성해보자. db 연결 후 간단한 query 문을 실행하는 sql문이다.

{% include codeHeader.html name="dbtest.jsp" %}

<%@ page import="java.sql.*" contentType="text/html; charset=UTF-8" %>
<%@ page import="javax.naming.*" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <%
	String DB_URL = System.getenv("db_url");
	String DB_USER = System.getenv("db_user");
	String DB_PASSWORD= System.getenv("db_password");
	Connection conn;
	Statement stmt;
	ResultSet rs;

	try
	{
		out.println("START TRY!!");
		out.println("<br/>");
		conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
		out.println("getConnection success");
		out.println("<br/>");
		stmt = conn.createStatement();
		out.println("createStatement success");
		out.println("<br/>");
     	out.println("MySQL Connection Success!");
		out.println("<br/>");

		String sql = "select * from testTBL";
		rs = stmt.executeQuery(sql);
		
		while(rs.next()) {
			out.println("id - " +rs.getString("id")+ " |");
			out.println("NAME - " +rs.getString("name")+ " |");
			out.println("<hr>");
		}
		stmt.close();
		conn.close();
	}
	catch(Exception e)
	{
		e.printStackTrace();
	}
%>
 
</body>
</html>

‣ 로컬 파일 컨테이너 안으로 옮기기

.jsp를 tomcat container 안으로 옮기기

kubectl은 기본적으로 cp 명령어를 지원한다.

$ kubectl -n [namespace명] cp [옮길 file 경로] [pod명]:[pod내부 container의 목적지 경로]
# pod 조회
$ kubectl get pods -n was-prac
NAME                          READY   STATUS    RESTARTS   AGE
was-mysql-55f84c9c8b-hnq4l    1/1     Running   0          120m
was-tomcat-6c97566f78-5kljc   1/1     Running   0          17m

# 파일 옮기기
$ kubectl -n was-prac cp ./dbtest.jsp was-tomcat-6c97566f78-5kljc:/usr/local/tomcat/webapps/ROOT

파일이 정상적으로 넘어갔는지 확인

$ kubectl exec -n was-prac pod/was-tomcat-6c97566f78-7pj4g -- ls -lh /usr/local/tomcat/webapps/ROOT | grep 'dbtest'
-rw-r--r--. 1 root root 1.2K Oct  4 19:06 dbtest.jsp

‣ 웹에서 DB 연결 테스트

.jsp가 적용되었는지 확인해보자.
/[jsp 파일명]로 구분해 접속해볼 수 있다. url은 다음과 같다
http://192.168.56.101:30002/dbtest.jsp

$ curl -X GET 192.168.56.101:30002/dbtest.jsp

Loading script...