如何在 Kubernetes 上部署 MySQL

本教程展示了在 Kubernetes 上部署 MySQL 的詳細步驟。 我將在這裡使用 minikube 來演示 Kubernetes MySQL 示例。

我們都知道數據持久性的重要性,而且幾乎我們所有的應用程序都非常依賴某種數據庫管理系統 (DBMS)。 在 Kubernetes 上設置 DBMS 有助於 DevOps 團隊和數據庫管理員輕鬆利用和擴展數據庫。

準備環境

按照本教程,您需要在 Ubuntu Linux 上安裝 Minikube。

您可以通過以下命令驗證 Minikube 是否已成功啟動並運行:

$ minikube status

輸出:

minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

為 MySQL 創建 Secret

Kubernetes 使用 Secret 存儲和管理敏感信息,例如密碼、ssh 密鑰和 OAuth 令牌。 在本教程中,我們使用 base64 編碼以存儲“MYSQL_ROOT_PASSWORD”。 為了 example:

$ echo -n 'admin' | base64

輸出:

YWRtaW4=

創建一個 mysql-secret.yaml 將被映射為環境變量的 MySQL 文件,如下所示:

apiVersion: v1
kind: Secret
metadata:
name: mysql-pass
type: Opaque
data:
password: YWRtaW4=

應用清單:

$ kubectl create -f mysql-secret.yaml

secret/mysql-pass created

驗證 Secret 剛剛創建成功:

$ kubectl get secrets

NAME TYPE DATA AGE
default-token-l7t7b kubernetes.io/service-account-token 3 4h24m
mysql-pass Opaque 1 1m

部署 MySQL

創建 mysql-pod.yaml 在 Kubernetes 集群上部署 MySQL pod 的文件:

apiVersion: v1
kind: Pod
metadata:
name: k8s-mysql
labels:
name: lbl-k8s-mysql
spec:
containers:
- name: mysql
image: mysql:latest
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
ports:
- name: mysql
containerPort: 3306
protocol: TCP
volumeMounts:
- name: k8s-mysql-storage
mountPath: /var/lib/mysql
volumes:
- name: k8s-mysql-storage
emptyDir: {}

應用清單文件:

$ kubectl create -f mysql-pod.yaml

pod/k8s-mysql created

驗證 pod 是否正在運行:

$ kubectl get pod

NAME READY STATUS RESTARTS AGE
k8s-mysql 1/1 Running 0 30s

現在,我們可以連接到 k8s-mysql 在下面:

$ kubectl exec k8s-mysql -it -- bash

root@k8s-mysql:/# echo $MYSQL_ROOT_PASSWORD
admin
root@k8s-mysql:/# mysql --user=root --password=$MYSQL_ROOT_PASSWORD
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or g.
Your MySQL connection id is 11
Server version: 8.0.22 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)

mysql>

Kubernetes 使用 Service 將 pod 暴露給其他 pod 或外部系統。 我們將使用以下清單文件 mysql-service.yaml 使 k8s-mysql pod 可達:

apiVersion: v1
kind: Service
metadata:
name: mysql-service
labels:
name: lbl-k8s-mysql
spec:
ports:
- port: 3306
selector:
name: lbl-k8s-mysql
type: ClusterIP

應用清單以創建服務:

$ kubectl create -f mysql-service.yaml

service/mysql-service created

驗證服務是否已成功創建:

$ kubectl get svc

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 443/TCP 5h4m
mysql-service ClusterIP 10.110.22.182 3306/TCP 30s

創建一個 NodeJS Api 來訪問 mysql

為了能夠從另一個 pod 連接到 mysql,我們需要有 IP 我們的 pod 的地址可以使用:

$ kubectl get pod k8s-mysql -o template --template={{.status.podIP}}

172.17.0.5

好的,現在我要創建一個示例 NodeJS 應用程序,為了在數據庫 MESSAGES 表中存儲一組消息,應用程序將有兩個端點:

  • ‘/ping’:檢查服務器健康狀況
  • ‘/msg-api/all’: 獲取所有存儲的消息

為簡單起見……該表將只有一列名為 TEXT。

首先,節點應用程序:

// api.js -> 端點在這裡

var express = require('express')
var mysql = require('mysql')

var Router = express.Router();
var ConnPool = mysql.createPool({
host: '172.17.0.5',
user: 'root',
password: 'admin',
database: 'k8smysqldb'
})

// create database and MESSAGE table if not exist
ConnPool.query('CREATE DATABASE IF NOT EXISTS k8smysqldb', function (err) {
if (err) throw Error('nt **** error creating database **** ' + err)

console.log('nt ==== database k8smysqldb created !! ====')

ConnPool.query('USE k8smysqldb', function (err) {
if (err) throw Error('nt **** error using database **** ' + err);

console.log('nt ==== database k8smysqldb switched !! ====')

ConnPool.query('CREATE TABLE IF NOT EXISTS messages('
+ 'id INT NOT NULL AUTO_INCREMENT,'
+ 'PRIMARY KEY(id),'
+ 'text VARCHAR(100)'
+ ')', function (err) {
if (err) throw Error('nt **** error creating table **** ' + err);
})
})
})

/**
* /all
*/
Router.get('/all', function (req, res) {
ConnPool.getConnection(function (errConn, conn) {
if (errConn) throw Error('error get connection : ' + errConn)

conn.query('SELECT * FROM messages', function (errSelect, rows) {
if (errSelect) throw Error('error selecting messages : ' + errSelect)
res.writeHead(200, {
'Content-Type': 'application/json'
});
var result = {
success: true,
rows: rows.length,
}
res.write(JSON.stringify(rows));
res.end();
})
})
})

module.exports = Router

// server.js -> 啟動 expressjs 服務器

var express = require('express')
var msgApi = require('./api')

var app = express()

app.use('/msg-api', msgApi)

app.get('/ping', function (req, res) {
res.write("hello there! I m up and running!");
res.end();
})

app.listen(8080, function () {
console.log('nt ==== Message API listening on 8080! ====')
})

// Dockerfile -> 為我們的應用程序捆綁 docker 鏡像

FROM node:latest

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

COPY package.json /usr/src/app/package.json
RUN npm i

COPY . /usr/src/app/

EXPOSE 8080
CMD [ "node", "server.js" ]

現在我們可以從 Dockerfile 構建我們的 docker 鏡像:

$ docker build -t linoxide/msg-api:v0.0.3 . --no-cache=true

Sending build context to Docker daemon 5.12kB
Step 1/8 : FROM node:latest
---> 2d840844f8e7
Step 2/8 : RUN mkdir -p /usr/src/app
---> Using cache
---> 1c29cda3dcd8
Step 3/8 : WORKDIR /usr/src/app
...

並將構建的鏡像推送到 Docker Hub:

$ docker push linoxide/msg-api:v0.0.3

The push refers to a repository [docker.io/linoxide/msg-api]
c4477a160652: Pushed
32c1bac97782: Pushed
3d629e3d2e5a: Pushed
...

v1: digest: sha256:dba64e7ff64561f4af866fbbb657555cad7621688c7f312975943f5baf89efa2 size: 2628

現在我們可以創建我們的 NodeJS 應用程序的 pod,下面的規範文件 msg-api-pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: k8s-msg-api
labels:
name: lbl-msg-api
spec:
containers:
- name: msg-api
image: linoxide/msg-api:v0.0.1
ports:
- name: msg-api

應用清單:

$ kubectl create -f msg-api-pod.yaml

pod/k8s-msg-api created

通過檢查狀態確保 pod 正在運行:

$ kubectl get pod

NAME          READY     STATUS    RESTARTS   AGE
k8s-msg-api   1/1       Running   0          22s
k8s-mysql     1/1       Running   0          1h

在這個級別,我們需要公開創建的 pod,以便可以從外部訪問它。 這次我將只使用命令行而不是規範文件:

$ kubectl expose pod k8s-msg-api --port=8080 --name=k8s-srv-msg-api --type=NodePort

service/k8s-srv-msg-api exposed

使用nodejs api從mysql數據庫中獲取數據

在這個層面,我需要指出一些重要的東西,為了理解所有的部分,讓我們首先總結一下我們到目前為止所做的事情,到目前為止,我們已經創建了一個 MySQL pod,我們已經通過一個服務暴露了它其他 pod 可以訪問它,其次,我們創建了一個示例 nodejs 應用程序,我們將其稱為消息傳遞 api,以便我們可以使用它來訪問 MySQL pod; 同樣,為了能夠訪問消息傳遞 API,我們需要通過服務公開它,我希望一切都清楚,直到這裡!

現在的問題是我們如何從集群外部主要調用 minikube 的消息傳遞 API? 為此,我們需要我們節點的 IP 地址,因為我使用的 minikube 只創建一個節點,所以 IP 地址被解析,是 minikube ip 地址本身,只需運行:

$ minikube ip

192.168.99.100

那麼港口呢? 好問題! 讓我們描述我們的消息傳遞 api 服務來檢查一下:

$ kubectl describe service k8s-srv-msg-api

Name:           k8s-srv-msg-api
Namespace:      default
Labels:         name=lbl-msg-api
Selector:       name=lbl-msg-api
Type:           NodePort
IP:         10.0.0.170
Port:           <unset> 8080/TCP
NodePort:       <unset> 30887/TCP
Endpoints:      172.17.0.6:8080
Session Affinity:   None
No events.

所以我們有 Port,它是我們的消息 API 服務的端口。 NodePort 是暴露的服務可用(可訪問)的端口,即服務可用 NodeIP:NodePort

讓我們試試看:

$ curl 192.168.99.100:30887/ping

hello there! I m up and running!%

$ curl 192.168.99.100:30887/msg-api/all

[]%

非常好,到目前為止我們能夠訪問我們的 MySQL 數據庫,讓我們使用終端將一些數據插入到我們的數據庫中。

$ kubectl exec k8s-mysql -it -- bash

root@k8s-mysql:/# mysql --user=root --password=$MYSQL_ROOT_PASSWORD
mysql: [Warning] Using a password on the command line interface can be insecure.
...

mysql> use k8smysqldb;

Reading table information for completion of table and column names

You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+----------------------+
| Tables_in_k8smysqldb |
+----------------------+
| messages             |
+----------------------+
1 row in set (0.01 sec)

mysql> insert into messages(text) values ('this is the first msg!');

Query OK, 1 row affected (0.01 sec)

mysql> insert into messages(text) values ('this is the second msg!');

Query OK, 1 row affected (0.01 sec)

讓我們使用 nodejs API 獲取這些數據 curl:

$ curl 192.168.99.100:30887/msg-api/all

[{"id":1,"text":"this is the first msg!"},{"id":2,"text":"this is the second msg!"}]%

結論

將 MySQL 數據庫容器化並在 Kubernetes 集群上運行 DBMS 為 DevOps 團隊帶來了很多好處,例如跨環境的可移植性、更容易啟動/停止和更新以及由於服務被隔離而具有更好的安全性。

感謝您的閱讀,請在下面的評論部分留下您的建議。