MySQL
kubernetes
Liberty
ibmcloudprivate

ICPにMySQLに接続するLibertyアプリケーションをデプロイ

お勉強のため、MySQLコンテナにアクセスするWebアプリケーション(WebSphere Liberty)コンテナを作成し、シングルノード構成のIBM Cloud Private CEにデプロイしてみたメモ。

IBM Cloud Private CEのインストールは以下の記事を参照。Kubernetes環境であればIKS(IBM Cloud Kubernetes Service)でもMinikubeでもGKEでも何でもよい。

AWSにIBM Cloud Private Community Editionをインストールする

Namespaceの作成

今回のリソースはns3というNamespaceに作成するので、Namespaceを作成する。

kubectl create ns ns3

MySQLコンテナの作成

以下を参考にする。

https://kubernetes.io/docs/tasks/run-application/run-single-instance-stateful-application/

PV/PVCの作成

今回は簡単に試すためPVとしてはhostPathで使用するので、ディレクトリーを用意する。NFSが利用できればその方が望ましい。

root@myicp01:/export# pwd
/export
root@myicp01:/export# mkdir mysql
root@myicp01:/export# ls -l
total 16
drwxrwxrwx  2 root root 4096 Jul  5 01:25 logstash
drwxr-xr-x 15 root root 4096 Jul  9 04:41 MC_jenkins02
drwxr-xr-x  3 root root 4096 Jul  4 07:15 MC_microclimate02
drwxr-xr-x  2 root root 4096 Jul  9 06:51 mysql
root@myicp01:/export#

MySQLから使用するPesistentVolumeを作成する。

mysql-pv.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
  name: mysql-pv
spec:
  storageClassName: mysql
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/export/mysql"
kubectl apply -f mysql-pv.yaml

PesistentVolumeClaimを作成する。ns3というNamespaceに全てのリソースを作成したいので、metadatanamespace: ns3を記載している。この後のリソースも同様。なお、PVは全てのNamespaceで共通のリソースであり、Namespace毎には作成できない。

mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
  namespace: ns3
spec:
  storageClassName: mysql
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
kubectl apply -f mysql-pvc.yaml

Secretの作成

MySQLのコンテナはrootパスワードを環境変数として渡す必要があるので、パスワードを保管するSecretを作成する。

パスワードをBase64でエンコードする。

$ echo -n "password" | base64
cGFzc3dvcmQ=

Secretを作成する。

mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
  namespace: ns3
type: Opaque
data:
  root-password: cGFzc3dvcmQ= # base64エンコードしたパスワード
kubectl apply -f mysql-secret.yaml

Deploymentの作成

MySQLのデプロイメントを作成する。先ほど作成したSecretから環境変数を定義している。

mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: ns3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.7
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: root-password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pvc
kubectl apply -f mysql-deployment.yaml

Serviceの作成

MySQLのServiceを作成する。アプリケーションはこのServiceに接続する。

mysql-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: ns3
spec:
  ports:
  - port: 3306
  selector:
    app: mysql
kubectl apply -f mysql-service.yaml

MySQLコンテナの初期化

MySQLコンテナにユーザーを作成し、テーブルを作成し、初期データをロードする。
本来はJobで実行するのが望ましいかもしれない。ここではここではkubectl execで実施する。

(補足)
環境変数MYSQL_DATABASEや、MYSQL_USERMYSQL_PASSWORDにより、コンテナの初回起動時にデータベースを1つ作成したり、ユーザーをひとり作ることも可能。また、/docker-entrypoint-initdb.d/ディレクトリーにスクリプトを配置して、コンテナの初回起動時に初期化処理を行う事も可能。

2.5.8.2 More Topics on Deploying MySQL Server with Docker

MySQLのPodを確認する。

kubectl get po -n ns3

Podにログインする。

kubectl exec -it -n ns3 <Pod名> bash

(参考)
以下でラベル名からPodを探してログインできる。

kubectl exec -it -n ns3 $(kubectl get po -n ns3 -l app=mysql -o jsonpath="{.items[0].metadata.name}") bash

mysqlクライアントを実行する。パスワードはSecretに定義したパスワードを入力する。

mysql -u root -p

mydbデータベースを作成する。

create database mydb;

libertyユーザーを作成する。パスワードはliberty

create user 'liberty' identified by 'liberty';

libertyユーザーにmydbへの全権限を付与する。

grant all privileges on mydb.* to 'liberty';

一度抜けてlibertyユーザーで入り直す。

quit;
mysql -u liberty -p

mydbにmemberテーブルを作成する。

use mydb;
DROP TABLE IF EXISTS member;
CREATE TABLE `member` (
  id int NOT NULL AUTO_INCREMENT,
  name varchar(255) NOT NULL,
  created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
);

テストデータを挿入する。

INSERT INTO member (name) VALUES ('user1'),('user2'),('user3');
INSERT INTO member (name) VALUES ('user4'),('user5'),('user6');

データを確認。

SELECT * from member;
quit;

Libertyコンテナのデプロイ

Secretの作成

MySQLに接続するユーザーのパスワードを格納するためのSecretを作成する。

パスワードをBase64でエンコードする。

$ echo -n "liberty" | base64
bGliZXJ0eQ==

Secretを作成する。

liberty-mysql-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: liberty-mysql-secret
  namespace: ns3
type: Opaque
data:
  liberty-password: bGliZXJ0eQ==
kubectl apply -f liberty-mysql-secret.yaml

ConfigMapの作成

パスワード以外のMySQLへの接続情報を格納したConfigMapを作成する。envではなくenvFromを使ってまとめて渡したいので、Keyには環境変数名を使用する。

liberty-mysql-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: liberty-mysql-config
  namespace: ns3
data:
  MYSQL_SERVERNAME: "mysql"
  MYSQL_PORTNUMBER: "3306"
  MYSQL_USER: "liberty"
kubectl apply -f liberty-mysql-config.yaml

Libertyコンテナイメージの作成

MySQLに接続して取得したデータを表示するアプリケーションを乗せたLibertyコンテナを作成する。

説明が面倒なので要点のみ記載。ローカルでMySQLを動かして稼働確認した方がよいでしょう。

Eclipseで「Dynamic Web Project」を作成し、以下のServletを作成。WARファイルとしてエクスポートする。

HelloServlet.java
package com.example;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

/**
 * Servlet implementation class HelloServlet
 */
@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * Default constructor.
     */
    public HelloServlet() {
        // TODO Auto-generated method stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html; charset=SJIS");
        PrintWriter writer = response.getWriter();

        DataSource dataSource = null;
        Connection conn = null;

        try {
            Context context = new InitialContext();
            dataSource = (DataSource) context.lookup("jdbc/mydb");
        } catch (NamingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new ServletException(e);
        }

        try {
            conn = dataSource.getConnection();

            PreparedStatement statement = conn.prepareStatement("select name from member");
            ResultSet result = statement.executeQuery();
            while (result.next()) {
                writer.println(result.getString(1));
                System.out.println(result.getString(1));
            }

        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                if (conn != null)
                    conn.close();
            } catch (SQLException e) {
            }
        }

        writer.append("Served at: ").append(request.getContextPath());
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

WARファイルをエクスポートしたディレクトリーに、MySQLのJDBCドライバーのjarファイルを配置する。

https://dev.mysql.com/downloads/connector/j/

このLibertyのserver.xmlを配置する。MySQLへの接続情報は環境変数で渡すようにする。

server.xml
<server description="new server">

    <!-- Enable features -->
    <featureManager>
        <feature>webProfile-7.0</feature>
        <feature>localConnector-1.0</feature>
    </featureManager>

    <!-- To access this server from a remote client add a host attribute to 
        the following element, e.g. host="*" -->
    <httpEndpoint host="*" httpPort="9080" httpsPort="9443"
        id="defaultHttpEndpoint" />

    <library id="MySQLLib">
        <fileset dir="resources/mysql" includes="*.jar" />
    </library>

    <dataSource jndiName="jdbc/mydb" transactional="false">
        <jdbcDriver libraryRef="MySQLLib" />
        <properties databaseName="mydb" serverName="${env.MYSQL_SERVERNAME}"
            portNumber="${env.MYSQL_PORTNUMBER}" user="${env.MYSQL_USER}" password="${env.MYSQL_PASSWORD}" />
    </dataSource>

    <!-- Automatically expand WAR files and EAR files -->
    <applicationManager autoExpand="true" />

    <applicationMonitor updateTrigger="mbean" />

</server>

同じディレクトリーにDockerfileを作成する。

FROM websphere-liberty:webProfile7
COPY server.xml /config/
RUN installUtility install --acceptLicense defaultServer
COPY mysql-connector-java-8.0.11.jar /config/resources/mysql/
COPY hellomysql.war /config/dropins/

このディレクトリーでDockerビルドを実行し、DockerHubにPushする。

docker build -t hellomysql:0.1 .
docker tag hellomysql:0.1 sotoiwa540/hellomysql:0.1
docker push sotoiwa540/hellomysql:0.1

Libertyコンテナのデプロイ

Libertyコンテナをデプロイする。

hellomysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellomysql
  namespace: ns3
spec:
  selector:
    matchLabels:
      app: hellomysql
  replicas: 1
  template:
    metadata:
      labels:
        app: hellomysql
    spec:
      containers:
      - name: hello
        image: sotoiwa540/hellomysql:0.1
        imagePullPolicy: Always
        ports:
        - containerPort: 9080
        env:
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: liberty-mysql-secret
              key: liberty-password
        envFrom:
        - configMapRef:
            name: liberty-mysql-config
kubectl apply -f hellomysql-deployment.yaml

Serviceの作成

Serviceを作成する。

hellomysql-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hellomysql
  namespace: ns3
spec:
  type: ClusterIP
  selector:
    app: hellomysql
  ports:
  - protocol: TCP
    port: 9080
kubectl apply -f hellomysql-service.yaml

Ingressの作成

Ingressを作成する。

hellomysql-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: hellomysql
  namespace: ns3
  annotations:
    ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host:
    http:
      paths:
      - path: /app1
        backend:
          serviceName: hellomysql
          servicePort: 9080
kubectl apply -f hellomysql-ingress.yaml

稼働確認

以下のURLにアクセスし、アプリケーションがMySQL内のデータを取得して表示することを確認する。

https://<ICPのProxyのVIP>/app1/hellomysql/HelloServlet