▶︎ 개요
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
위 명령어 입력 후에는 아래와 같은식으로 접속이 된다.
MySQL 접속
$ mysql -uroot -p
bash-4.4# mysql -uroot -p
Enter password:
접속하면 아래와 같은 화면을 볼 수 있을 것이다.
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로 설정
‣ 톰캣 이미지 빌드
아래와 같이 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에 정상적으로 업로드 되었는지 확인해보자.
‣ 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