引言#

一直想要学会k8s,但是事实上来说学习这个技术并不是靠bilibili的教学就能够学会的,更何况很多时候都会忘记,暑假时候我看完了B站的课,但有种理论知识会了实际不会的无力感,同时更有种知识一点点逝去的害怕,于是在强哥的支持下(白嫖服务器,白嫖域名😍😍),完成了k8s的部署

同时我这一套配置可是any亲传,后继有人呐……

项目原仓库

步骤#

  1. 使用 Dockerfile 打包镜像并 pushdockerHub
  2. 部署 mysql\pgsql\redis 这些我的应用所需要的依赖
  3. 拉取刚刚提交的镜像并启动一个 pod
  4. 暴露我的端口并让外界所访问

实操#

首先需要下载 LensTermius 以及一个文件夹,后续的一切操作都在这两个部分操作

最终的目录应该是这样的:#

image-20240928204532041

这个是本地文件夹下的目录,然后会在 Lens 操作生成Pod,很好用的(等会再说)

我的Dockerfile:#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
FROM golang:1.22 AS builder

COPY . /build

WORKDIR /build

RUN set -ex \
&& GO111MODULE=auto CGO_ENABLED=0 go build -ldflags "-s -w -extldflags '-static' -X 'gin-quickly-template/pkg/ \
version.SysVersion=$(git show -s --format=%h)'" -o App

FROM alpine:3.14.0

WORKDIR /Serve

COPY --from=builder /build/App ./App
# plz replace with true host

# if you want to push to k3s, plz annotate it
#COPY --from=builder /build/config.yaml ./config.yaml


RUN apk update && apk add tzdata \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone


ENTRYPOINT ["/Serve/App","server"]

其中 COPY --from=builder /build/config.yaml ./config.yaml 这个语句表示把配置文件移到 workDir 下,如果没有配置文件,单个容器是运行不起来的。但是在k3s中,配置文件是被 configMap 管理的,所以不需要把配置文件移到 workDir 下(这样子配置文件是焊死的)

推送到DockerHub:#

直接问Gpt,这一块没什么重点

1
2
3
4
docker login   # 没有登录先登录
docker build -t gin-quickly-template:latest . # 打包成镜像
docker tag gin-quickly-template:latest ccstudyhyc/gin-quickly-template:latest #打标签关联
docker push ccstudyhyc/gin-quickly-template:latest # 推送到远程仓库

安装依赖#

然后就是安装依赖,因为强哥的服务器上啥都没有,同时我也没有啥空闲的数据库,所以只能自己起Pod

命名空间#

在我的部署中,所有的Pod都是在 hduhelp 这个命名空间中,特别是service的连接如果没有在同一个 namespace 中可能会出现大问题

Mysql#

启动一个Pod需要考虑那么几件事情

  1. 怎么编写deployment.yaml来自定义数据库
  2. 怎么在其他Pod连接到这个数据库Pod
  3. 怎么实现这个数据库Pod数据持久化

有这个三个问题我们就会明白为什么需要创建文件(在刚才图片的k8s/mysql目录下)

  • deployment-mysql.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-pod
namespace: hduhelp
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: rootpassword
- name: MYSQL_PORT
value: "3306"
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-storage
mountPath: /var/lib/mysql
- name: init-sql
mountPath: /docker-entrypoint-initdb.d/init.sql
subPath: init.sql
volumes:
- name: init-sql
configMap:
name: configmap-mysql
- name: mysql-storage
persistentVolumeClaim:
claimName: mysql-pvc

整体的逻辑肯定都能看懂,下面说一下比较重要的东西:

  1. volumeMounts:

    这里总共做了两次的卷映射,一个是sql初始化文件的映射,就是init.sql,这个文件把他挂载到/docker-entrypoint-initdb.d/init.sql下,可以让容器创建的时候有且仅有一次创建好需要的表,username,password,同时赋予这个username

    所有权限; 另一个是mysql数据的映射,就是会将 mysql容器/var/lib/mysql这个路径下的数据与我 pv中定义的数据相关联

  2. 关于env

​ 由于之前的username,password已经创建,所以这里只要写一个root_password,mysql定义必须要写这个字段,要么就要设置 可以为空

  • configmap-mysql.yaml + pv-mysql.yaml + pvc-mysql.yaml

这三个部分我觉得是联合在一起的,所以需要一起看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# pv-mysql.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: "/home/echin/hduhelp-pv/mysql-pv/data"


# pvc-mysql.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: manual


# configMap-mysql.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: configmap-mysql
namespace: hduhelp
data:
init.sql: |
CREATE USER 'username'@'%' IDENTIFIED BY 'password';
CREATE DATABASE IF NOT EXISTS mysql DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;
GRANT ALL PRIVILEGES ON mysql.* TO 'username'@'%' WITH GRANT OPTION;

这里有几个比较重要的点:

  1. pv和pvc是互相关联作用的,主要通过 storageClassName 来关联,他的这个关联方式是自动匹配,一个pv自动关联一个pvc
  2. hostPath: 这个需要我们在服务器内创建目录: "/home/echin/hduhelp-pv/mysql-pv/data"
  3. init.sql: 这个就是mysql的启动文件,如果是通过应用的orm去连接数据库他会显示连接不上,具体原因我没有细究,后面直接使用any的这套配置了,好用
  • service-mysql.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: mysql-service
namespace: hduhelp
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
nodePort: 30306
type: NodePort

这里的重点就是 selector 就是service会与pod进行关联,管理ip映射,一个pod访问另一个pod的服务就是通过访问service暴露出来的端口来实现的,同时这里不建议NodePort这个type,但是用了似乎也没事?

  • 大致流程:

首先启动deployment-mysql.yaml 来启动一个Pod,在启动的开始会运行 configMap-init.sql 中的 sql语句创建数据库和用户密码,同时他会把密码通过 pvc-pv-hostPath 的方式存储在本地里面,然后通过 service-mysql.yaml 将服务绑定到相应的service上供其他Pod使用

  • 启动:

进入Termius,创建pv映射目录

1
2
3
4
5
6
7
8
9
10
cd /home/echin

mkdir hduhelp-pv
cd hduhelp-pv

mkdir mysql-pv
cd mysql-pv

mkdir data
cd data

直接通过 Lens 启动,不需要在Termius上大费周章

image-20240928211541230

1
2
cd  absolutelyPathToYourYaml
kubectl -n hduhelp apply -f .

验证我们成功运行了Pod:

映射远程端口到本地

QQ_1727529622694

打开我们的IDE或者DataGrip:

QQ_1727529706254

成功连接

后面去查看我们的持久化数据卷是否映射成功:

image-20240928212311768

如图所示是映射成功的

Postgresql#

postgresql 的书写逻辑和 mysql 相同,迁移过来就行,记得创建"/home/echin/hduhelp-pv/pgsql-pv/data"

  • configmap-pgsql.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: configmap-pgsql
    namespace: hduhelp
    data:
    init.sql: |
    DO $$
    BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='username') THEN
    CREATE USER "username" WITH PASSWORD 'password';
    END IF;
    END
    $$;

    DO $$
    BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname='pgsql') THEN
    CREATE DATABASE "pgsql" ENCODING 'UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8' TEMPLATE template0;
    END IF;
    END
    $$;

    GRANT ALL PRIVILEGES ON DATABASE "pgsql" TO "username";
  • deployment-pgsql.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: pgsql-pod
    namespace: hduhelp
    spec:
    selector:
    matchLabels:
    app: pgsql
    template:
    metadata:
    labels:
    app: pgsql
    spec:
    containers:
    - name: postgres
    image: postgres:12
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 5432
    env:
    - name: POSTGRES_DB
    value: postgres
    - name: POSTGRES_USER
    value: username
    - name: POSTGRES_PASSWORD
    value: password
    volumeMounts:
    - name: pgsql-storage
    mountPath: /var/lib/postgresql/data
    - name: init-sql
    mountPath: /docker-entrypoint-initdb.d/init.sql
    subPath: init.sql
    volumes:
    - name: init-sql
    configMap:
    name: configmap-pgsql
    - name: pgsql-storage
    persistentVolumeClaim:
    claimName: pgsql-pvc
  • pv-pgsql.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: pgsql-pv
    spec:
    capacity:
    storage: 5Gi
    accessModes:
    - ReadWriteOnce
    persistentVolumeReclaimPolicy: Retain
    storageClassName: manual
    hostPath:
    path: "/home/echin/hduhelp-pv/pgsql-pv/data"
  • pvc-pgsql.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: pgsql-pvc
    spec:
    accessModes:
    - ReadWriteOnce
    resources:
    requests:
    storage: 5Gi
    storageClassName: manual
  • service-pgsql.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    apiVersion: v1
    kind: Service
    metadata:
    name: pgsql-service
    namespace: hduhelp
    spec:
    selector:
    app: pgsql
    ports:
    - port: 5432
    targetPort: 5432
    nodePort: 30432
    type: NodePort

Redis#

Redis 同上 ,记得创建 "/home/echin/hduhelp-pv/redis-pv/data"

  • deployment-redis.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    metadata:
    name: redis-deployment
    namespace: hduhelp
    spec:
    selector:
    matchLabels:
    app: redis
    template:
    metadata:
    labels:
    app: redis
    spec:
    containers:
    - name: redis
    image: redis:6.2.5
    ports:
    - containerPort: 6379
    volumeMounts:
    - name: redis-storage
    mountPath: /data
    volumes:
    - name: redis-storage
    persistentVolumeClaim:
    claimName: redis-pvc
  • pv-redis.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: redis-pv
    spec:
    capacity:
    storage: 5Gi
    accessModes:
    - ReadWriteOnce
    persistentVolumeReclaimPolicy: Retain
    storageClassName: manual
    hostPath:
    path: "/home/echin/hduhelp-pv/redis-pv/data"
  • pvc-redis.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: redis-pvc
    spec:
    accessModes:
    - ReadWriteOnce
    resources:
    requests:
    storage: 5Gi
    storageClassName: manual
  • service-redis.yaml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    apiVersion: v1
    kind: Service
    metadata:
    name: redis-service
    namespace: hduhelp
    spec:
    selector:
    app: redis
    ports:
    - port: 6379
    targetPort: 6379
    nodePort: 30637
    type: NodePort
  • App (gin-quickly-template)

下面要创建真正的应用了

首先应用要启动需要所谓的配置文件:

  • config-app.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
apiVersion: v1
kind: ConfigMap
metadata:
name: gin-quickly-template-config
namespace: hduhelp
data:
config.yaml: |
AppMode: "debug"
AppName: "gin-quickly-template"
Author: "echin"
Version: "v1.0.0"
Host: "localhost"
Port: "8080"
Log:
LogPath: "./"
CLS:
Endpoint: ""
AccessKey: ""
AccessToken: ""
TopicID: ""
Database:
Mode: "debug"
Mysql:
Host: "mysql-service"
Port: "3306"
Username: "username"
Password: "password"
DBName: "mysql"
Charset: "utf8mb4"
ParseTime: "True"
Loc: "Local"
Postgres:
Host: "pgsql-service"
Port: "5432"
Username: "username"
Password: "password"
DBName: "postgres"
SSLMode: "disable"
TimeZone: "Asia/Shanghai"
Redis:
Addr: "redis-service:6379"
Password: ""
DB: 0
Auth:
Secret: ""
Issuer: ""
Sentry:
Enable: true
Dsn: ""
OTEL:
Enable: true
ServiceName: ""
Endpoint: ""
AgentHost: ""
AgentPort: ""

注意这里的Host统一要改成service中定义的依赖名,比如说我在 mysql的service Pod中的 metadata 为 mysql-service,所以这里要写成 mysql-service

  • deployment-app.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: apps/v1
kind: Deployment
metadata:
name: gin-quickly-template
namespace: hduhelp
labels:
app: gin-quickly-template
spec:
replicas: 2
selector:
matchLabels:
app: gin-quickly-template
template:
metadata:
labels:
app: gin-quickly-template
spec:
containers:
- name: gin-quickly-template
image: ccstudyhyc/gin-quickly-template:latest
ports:
- containerPort: 8080
volumeMounts:
- name: config
mountPath: /Serve/config
volumes:
- name: config
configMap:
name: gin-quickly-template-config

这里要注意的是路径的绑定 - /Serve/config

在我的源程序中写着我的应用会读取 ./ 或者 ./config 下的配置文件,因此我这里选择了后者

我之前的写法是:

1
2
3
volumeMounts:
- name: config
mountPath: /Serve

以为这样子就可以将配置文件映射到/Serve下,但是事实是他这个configMap会覆盖掉原先存在的文件夹以及文件,因此他会报错:

4834e452ac5b801bc6feb682f96dd19c

其实还有一种写法应该也可以实现:

1
2
3
4
volumeMounts:
- name: config
mountPath: /Serve/config.yaml
subPath: config.yaml

🆗容器起来以后我们就需要让我们的容器暴露在集群内,就需要定义service

其实我一开始觉得不需要Service,但是后来我发现写Ingress是依赖service的,原理和Pod间调用一样,就是Pod的创建删除过于频繁,用service来自动管理Ip连接

  • service-app.yaml
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: gin-quickly-template-service
spec:
selector:
app: gin-quickly-template
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: ClusterIP

这里不写NodePort的原因就是感觉写NodePort太蠢了…………….

然后可以测试一下是否启动成功(前提是你的依赖已经全部部署):

启动部署:

image-20240928231646803

端口转发到本地:

image-20240928233153445

测试:

image-20240928233243005

成功了!

最后需要把集群内的服务暴露出去,这个可以通过Ingress的traefic控制器代理出去

  • Ingress-traefic.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: traefik-ingress
namespace: hduhelp
spec:
ingressClassName: traefik
rules:
- host: Dns # 必须是域名,not ip
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gin-quickly-template-service
port:
number: 8080

Ingress 搭载的是 service,所以必须要搭建刚才的service

最后测试一下:

1
curl http://dsn/v1/ping

结果:

image-20240928232711590

至此 k3s 部署完毕!

总结#

  1. configMap 的路径映射
  2. init.sql 的巧妙使用
  3. service 的通过 selector 的label 匹配
  4. 简单的 kubectl 命令行
  5. pv 和 pvc 的用法
  6. 强哥 和 any 的教导和支持😘😘😘