0%

其实只是想记录一下如何在application.yaml中使用Linux Environement。

因为已经使用了Helm进行应用装配,那么动态的配置只能通过Linux Environment注入到SpringBoot中,让其能够读取Environment是非常关键的。

其实读取Linux Environment很简单,直接变理引用即可:

1
jdbc.url: ${DB_NAME}:8080/demo?

这里DB_NAME是env定义的环境变量,在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
spring:
application:
name: demo
jackson:
time-zone: Asia/Shanghai
date-format: yyyy-MM-dd HH:mm:ss
property-naming-strategy: SNAKE_CASE
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
show_sql: true
data:
jpa:
repositories:
enabled: true
datasource:
url: jdbc:mysql://${DB_SERVER}:3306/${DB_NAME}?characterEncoding=utf8&autoReconnect=true&useUnicode=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2b8
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
type: com.zaxxer.hikari.HikariDataSource
hikari:
auto-commit: false
transaction:
rollback-on-commit-failure: true

其实我个人觉得Kubernetes下的Network和PV是最难理解的,前者是因为我不熟悉网络,后者则是因为资料不足。

这一篇是记录PV的学习的,也把一些知识点记录下来。

一、前置条件

  1. 已安装配置好NFS Server,用于远程文件系统服务。
  2. 已了解Kubernetes中关于RBAC、Service、Deployment相关的知识。

二、概念

PV:存储卷。其实没接触过存储相关领域的人压根就不知道什么叫卷。如果类比的话,它可以和Windows下的一个磁盘分区类比,和Unix下的挂载分区类比。我们可以认为,它就是一个放在远端的可以随时挂载到容器里的一个分区。

PVCPV只是申明了一个分区,但是并不是直接可用的,它必须通过申请使用方可使用。举个例子,电影院里有很多座位(PV),如果我们要看电影,肯定不能直接进去看,那得买票,而PVC就是票,所以,在看电影之前(使用PV之前),我们需要买票(申请PVC)。

StorageClass:存储类别。可以简单的认为是存储类型,因为分布式的存储方案有很多,根据不同的方案就会衍生出不同的存储类别。

其实StorageClass在这里我还是不怎么懂,只是知道大概的意思,它是按照CNI协议实现的具体存储方案的实现。比方说NFS方案和GCLUSTER方案。

三、准备配置

环境参数:

1
2
NFS SERVER: 192.168.55.55
NFS 数据目录:/deploy/nfs

建立namespace monitoring

1
$ kubectl create namespace monitoring

配置RBAC

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
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
namespace: monitoring
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-provisioner-runner
namespace: monitoring
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get","create","list", "watch","update"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["nfs-provisioner"]
verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: monitoring
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io

上面配置为:

  1. 建立了一个 ServiceAccount:nfs-provisioner
  2. 建立一个ClusterRole,用于绑定相关的权限:nfs-provisioner-runner
  3. 将ClusterRole nfs-provisioner-runner 绑定到 ServiceAccount nfs-provisioner

这里准备的东西是为了将来准备PV时使用的。

配置NFS Provisioner

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
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-client-provisioner
namespace: monitoring
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-client-provisioner
image: quay.io/external_storage/nfs-client-provisioner:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: sanlea/nfs
- name: NFS_SERVER
value: 192.168.55.55
- name: NFS_PATH
value: /deploy/nfs
volumes:
- name: nfs-client-root
nfs:
server: 192.168.55.55
path: /deploy/nfs

这里provisioner主要是对sanlea/nfs的PROVISIONER_NAME的StorageClass提供支持,并且配置了NFS相关的信息,如IP和布署目录等。

这一步很重要,这里做的是为StorageClass提供技术实现,也就是说,后面绑定的StoreClass的操作,都是通过这里布署的provisioner去实际操作的。

配置StorageClass

1
2
3
4
5
6
7
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs
namespace: monitoring
provisioner: sanlea/nfs
reclaimPolicy: Delete

这里配置了一个名为nfsStorageClass,这里把这个StorageClass的provisioner设置成我们之前配置好的sanlea/nfs,这样,后面所有使用了nfs作为StorageClassPVPVC都会调用provisioner为sanlea/nfs服务来实现存储。

四、使用

上面的配置了StorageClass,但是一直没出现之前说的PVPVC,一直觉得很奇怪。

在这里,我们利用了provisioner的特性,我们只要配置好PVC,而PV则由provisioner自动创建,也就是说,每一个PVC会自动创建一个PV与之对应,也得出一个结论,PV在这里是一次性的,只要PVC被删除了,PV也随之被删除了。

其实PV里的数据并没有随着PVC的删除而删除,provisioner通过更改目录名的方式备份了这些数据,这些数据是需要人为删除方可真正删除的。

定义PVC

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: helm-server-claim
namespace: infra
spec:
storageClassName: nfs
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi

定义一下使用这个PVC的Pod,挂载到自己的数据目录中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
kind: Pod
apiVersion: v1
metadata:
name: helm-server
namespace: infra
spec:
containers:
- name: helm-server
image: chartmuseum/chartmuseum:latest
args:
- "--storage=local"
- "--storage-local-rootdir=/data"
- "--port=8080"
volumeMounts:
- name: nfs-pvc
mountPath: "/data"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: helm-server-claim

五、总结

我知道,这只是PV里很小的一部分,其实大多数资料都说的不清楚,没办法。

其实实现了上述的配置已经基本满足的我憋大招的需求了。

一、前置条件

  1. 配置好NFS Server
  2. 配置好NFS StorageClass

二、配置

配置PVC

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: helm-server-claim
namespace: infra
spec:
storageClassName: nfs
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi

配置Deployment

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
kind: Deployment
apiVersion: apps/v1
metadata:
name: helm-server
namespace: infra
spec:
replicas: 1
selector:
matchLabels:
app: helm-server
template:
metadata:
labels:
app: helm-server
spec:
containers:
- name: chartmuseum
image: chartmuseum/chartmuseum:latest
args:
- "--storage=local"
- "--storage-local-rootdir=/data"
- "--port=8080"
volumeMounts:
- name: nfs-pvc
mountPath: /data
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: helm-server-claim

Chartmuseum启动相关参数说明:

1
2
3
--storage=local  # 设置为本地存储,这里使用NFS挂载目录的方式,所以,对于Chartmuseum来说是无感知的
--storage-local-rootdir=/data # 设置/data目录为Chartmuseum的数据目录
--port=8080 # 指定端口为8080

配置Service

1
2
3
4
5
6
7
8
9
10
11
12
13
kind: Service
apiVersion: v1
metadata:
name: helm-server
namespace: infra
spec:
selector:
app: helm-server
type: ClusterIP
ports:
- name: default
port: 80
targetPort: 8080

三、测试

直接通过域名访问:

1
http://helm-server.infra.svc.cluster.local

配置helm

1
2
3
$ helm repo add development http://helm-server.infra.svc.cluster.local
$ helm update
$ helm search repo demo

上传helm

1
curl --data-binary '@demo-0.1.0.tgz' http://helm-server.infra.svc.cluster.local/api/charts

安装

1
helm install -f demo.yaml demo development/demo

四、参考资料

1
https://chartmuseum.com/docs/

1、安装

1
# kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc1/aio/deploy/recommended.yaml

2. 配置用户

创建帐号

1
2
3
4
5
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard

配置帐号角色

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard

获取登录token

1
# kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')

3、访问

1
https://kubernetes-dashboard.kubernetes-dashboard.svc.cluster.local

怎么会这么简单呢?嘿嘿!!

由于Helm的原因,需要在docker container内执行helm来安装应用,但是helm没有提供相关的API,所以就只能调用命令来实现了。

很简单,直接使用Runtime和Process,不过要注意Process并不是立马返回exit code,需要调用waitFor方法阻塞等待Process执行完返回code,通过code的不同,以确定应该是从标准输出获取结果还是从标准错误获取结果。

列代码胜千言:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@PostMapping(value = "/exec", produces = "text/plain")
public String exec(@RequestBody String command) throws Exception {
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
int code = process.waitFor();

String content;
if (code == 0) {
content = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
} else {
content = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
}

return content;
}

由于学习PV的需要,需要一个远程文件系统做基础设施,最简单的是NFS,这里写一下笔记,记录一下。

1、安装服务器软件

1
# dnf -y install nfs* rpcbind

2、配置NFS

创建NFS共享目录

1
# mkdir /deploy/nfs

配置NFS

/etc/exports:

1
2
/deploy/nfs 192.168.0.0/16(rw,no_root_squash,no_all_squash,sync)
/deploy/nfs 10.0.0.0/8(rw,no_root_squash,no_all_squash,sync)

使NFS配置生效:

1
# exportfs -r

3、启动NFS相关服务

1
2
3
4
5
# systemctl enable rpcbind.service
# systemctl enable nfs-server.service

# systemctl start rpcbind.service
# systemctl start nfs-server.service

4、检查配置情况:

1
2
3
4
5
6
7
# showmount -e 192.168.55.55
Export list for 192.168.55.55:
/deploy/nfs 10.0.0.0/8,192.168.0.0/16

# exportfs
/deploy/nfs 192.168.0.0/16
/deploy/nfs 10.0.0.0/8

5、挂载测试

1
# mount -t nfs 192.168.55.55:/deploy/nfs /root/test

发自内心的感到不妙,或许年后就要被辞退。

这个外包公司有一个非常不好的天性,没有人情味,不论你对这个公司做过多大的贡献,只要它不需要你了,它会毫不犹豫地把你去掉。

我为这个公司做了两个很关键的项目,客户是宝洁,得到A方的认可,但是,在自己公司这一方,我觉得难以置信,这个公司的企业文化太让人不可思议了。

担心,还有些恐惧。

……

但,

担心没有用,该来的还会来。

既然无法逃避,并且一定会发生,那就准备准备了。

因为Helm没有提供Java的API调用实现,所以只能通过Java调Shell Command的方式来调用Helm。

但是OpenJDK 的Docker Image没有这样的,需要自己去搞一个。

1. 首先下载相关的helm和kubectl

下载kubectl:

1
wget https://storage.googleapis.com/kubernetes-release/release/v1.17.0/bin/linux/amd64/kubectl

下载helm:

1
wget https://get.helm.sh/helm-v3.0.2-linux-amd64.tar.gz

2. 打包构建

将下载的文件放在同一个目录。

需要将helm的tar.gz文件解压,将里面的helm的文件提取出来,放在目标目录里,我们仅需要helm这个文件即可。

创建Dockerfile:

1
2
3
4
5
6
7
8
9
FROM openjdk:11

RUN mkdir -p /usr/local/bin
ADD kubectl /usr/local/bin
RUN chmod +x /usr/local/bin/kubectl
ADD helm /usr/local/bin
RUN chmod +x /usr/local/bin/helm

CMD bash

执行打包命令:

1
$ docker build . -t openjdk-helm:11

3. 总结

总是有解决问题的方法,无论再怎么难,只要专注这个问题,不断思考和寻求方案,总会解决掉的。

我是使用静态Pod方式部署的Gitlab,但是在部署完以后,主机名竟是Pod的名称,诸如gitlab-sanlea之类的,这样的主机名并不能正确访问,而且在代码库管理的时候很麻烦,需要将gitlab-sanlea改成Pod的IP或者Service的IP,非常不方便。

首先要跳到Gitlab的配置目录,一般情况下,在部署的时候按gitlab的官方文档,都会建议将config目录以volume的方式挂载,所以要进去那个目录,例如/deploy/data/gitlab/config,然后找到gitlab.rb文件,找到external_url,将其改成Service的IP或者域名,或者是Pod的IP,然后保存,例如:

1
external_url 'http://gitlab.infra.svc.cluster.local'

修改上述配置后,需要重新配置gitlab,这时并不是重启就可以的,需要在容器中执行命令才能行,怎么才能在Kubernetes Pod上执行一个命令呢?下面就是一个例子:

1
kubectl exec -c gitlab -n infra gitlab-sanlea gitlab-ctl reconfigure

上面的命令是,在名为gitlab-sanlea的Pod上执行gitlab-ctl reconfigure命令,-n infra指定命名空间,-c gitlab指定Pod里的容器。

执行上面的命令后,代码库的git仓库地址就正常了。

有些需求需要将Pod布署在固定的Node上面,这些Node不参与分配动态Pod,但是这些Node有非常强大固定的资源,主要用于布署诸如数据库、Redis等应用,因为这些应用需要大量读写磁盘,而PV并不适合这些应用,所以一般都是布署在固定的Node上,使用本地磁盘,提高读写速度。

而部署在固定Node上的Pod,我们称之为静态Pod。

一、布署

在所有Node中,都有一个目录叫/etc/kubernetes/manifests/,只要将Pod的定义yaml文件放进去即可自动启动一个静态Pod,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: v1
kind: Pod
metadata:
name: mysql
namespace: infra
labels:
name: mysql
spec:
containers:
- image: mysql:8
name: mysql
containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: %$338753kdkdjl^^
volumes:
- name: mysql
hostPath:
path: /deploy/data/mysql

二、注意事项

  1. 静态Pod不由master api server管理,也就是说,使用kubectl去删除静态pod是行不通的。
  2. 静态Pod和普通Pod一样,一样可以让Service匹配。