云原生部署常见服务
在开发过程中,经常需要自己进行一些小小的测试,亦或是简单的验证一些功能,简单功能使用公司的业务库还好,若是公司的业务库小众,或者测试的功能有危险情况,一般都希望能在隔离环境测试,虚拟机对于此场景太重了,用 Docker 轻量的启动一些服务非常方便。
该博客并非科普向,因此不再赘述 Docker、容器、Kubernetes 基础知识。
镜像拉取
内网环境一般公司会自行搭建 Harbor 镜像仓库,在拉取时配置 secret 秘钥即可。公网环境由于网络等限制,一般需要配置镜像加速,阿里云中提供了方法:阿里云配置镜像加速
1
| sudo mkdir -p /etc/docker
|
1 2 3 4 5
| sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://quype1o7.mirror.aliyuncs.com"] } EOF
|
1
| sudo systemctl daemon-reload
|
1
| sudo systemctl restart docker
|
有的时候,即使配置了这个也还是拉不下来,我一般都会去三方仓库(如:渡渡鸟镜像同步站)拉,找到自己想要的镜像后使用 docker pull 国内镜像下载:

1
| docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/fnproject/fn-java-fdk:jre11-1.0.211
|
拉下来后这个 tag 太长了,一般我会改成比较简单的 tag:
1
| docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/fnproject/fn-java-fdk:jre11-1.0.211 jre:11
|
有时我们内网下载不下镜像,希望在外网下载后导入到内网使用,使用如下命令导出为 tar 文件:
1
| docker save jre:11 -o jre11.tar
|
注意我用的 jre:11 如果这里使用镜像ID,在内网导入后会丢失 tag 信息,内网导入:
1
| docker load -i jre11.tar
|
其实部署服务时镜像的选择有很多知识,如果是想要绝对安全的环境(内部 curl、vim等指令均不存在),则使用只包含基础工具的镜像,像 Java 程序运行时其实只依赖 jre,如果使用 jdk 会导致最后的镜像体积很大,当然这也是一种权衡,有了 jdk 后可以定位一些问题。
Docker 运行 Mysql
数据库想必是开发中最常用的工具了,启动指令也非常简单:
1
| docker run --name mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql:5.7
|
这是个用完即丢的示例,因为这个指令并没有保存 mysql 的数据,非常适合测试,如果想要保存数据,则命令如下:
1
| docker run --name mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -v /data/mysql:/var/lib/mysql -d mysql:5.7
|
其中 -v /data/mysql:/var/lib/mysql 即把容器外的 /data/mysql 目录挂载到 容器内的 /var/lib/mysql 目录
注:不同版本的镜像数据目录、环境变量未必完全相同,使用时注意看文档。
Docker 运行 Redis
1
| docker run --name redis -d -p 6379:6379 redis:5.0.8
|
依然是用完即丢的示例,并且没有设置账号和密码,这种都倾向于用完即弃,生产环境千万不要这样
像一些常见的还有 consul、kafka 等….
Kubernetes 部署 Redis-Cluster 集群
Redis-Cluster 和 主从哨兵集群的区别与优劣就不在此比对了,本文是以部署为主。
- 从三方仓库拉取 Redis7 镜像:
1
| docker pull docker.1ms.run/library/redis:7
|
- 换一个简单的 tag
1
| docker tag docker.1ms.run/library/redis:7 redis:7
|
- 为了让业务更可控(对于陌生的镜像我们连 redis.conf 在哪都不清楚),基于这个镜像改造我们自己的镜像,首先去 github-redis-release 中找到对应的版本,接着找到里面的 redis.conf 文件
- 在有 docker 环境的机器上组成如下的结构(dockerfile 自行创建):
1 2 3
| └── redis ├── dockerfile └── redis.conf
|
- 编写 dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| FROM redis:7 as base
RUN mkdir -p /usr/local/etc
COPY redis.conf /usr/local/etc/redis.conf
RUN chmod 644 /usr/local/etc/redis.conf
EXPOSE 6379
CMD ["redis-server", "/usr/local/etc/redis.conf"]
|
将 dockerfile 构建成镜像:
1
| docker build -t redis:7.4 -f dockerfile .
|
- 写 Kubernetes 声明文件,注意事项如下:
- 第4行 redis-cluster 为命名空间的名字,不推荐修改,如果修改则需修改所有 namespace: redis-cluster 的地方
- 第9行 redis-cluster-config 是 redis.conf 中的一些属性,通过 74-79 行挂载进了容器的 redis.conf 文件中,requirepass 是连该节点的密码, masterauth 是主备间的密码
- 第31行与93行应保持一致
- 第85行对应的字符串应取 “kubectl get sc” 查询返回结果中的第一列(Name)值,sc 是用来真正帮忙创建存储目录的,有些公司可能有固态和机械等多种盘,会根据应用情况选择合适的 sc
- 第88行指定了 redis 存储的大小,根据使用量情况自行决定(redis的aof、rdb持久化机制),12行指定了 redis 工作目录为容器内的/data,82行指定了通过sc创建出来的路径的别名,64、65行将这个别名挂载到了容器的/data目录,不推荐修改这个目录,因为还涉及到15、18行等地方
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| apiVersion: v1 kind: Namespace metadata: name: redis-cluster --- apiVersion: v1 kind: ConfigMap metadata: name: redis-cluster-config namespace: redis-cluster data: redis-config: | appendonly yes protected-mode no dir /data port 6379 cluster-enabled yes cluster-config-file /data/nodes.conf cluster-node-timeout 5000 masterauth Fusion@123 requirepass Fusion@123 --- apiVersion: apps/v1 kind: StatefulSet metadata: name: redis-cluster namespace: redis-cluster labels: app.kubernetes.io/name: redis-cluster spec: serviceName: redis-headless replicas: 6 selector: matchLabels: app.kubernetes.io/name: redis-cluster template: metadata: labels: app.kubernetes.io/name: redis-cluster spec: containers: - name: redis image: 'redis:7.4' command: - "redis-server" args: - "/usr/local/etc/redis.conf" - "--protected-mode" - "no" - "--cluster-announce-ip" - "$(POD_IP)" env: - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP ports: - name: redis-6379 containerPort: 6379 protocol: TCP volumeMounts: - name: config mountPath: /usr/local/etc - name: pvc mountPath: /data resources: limits: cpu: '2' memory: 8Gi requests: cpu: 50m memory: 500Mi volumes: - name: config configMap: name: redis-cluster-config items: - key: redis-config path: redis.conf volumeClaimTemplates: - metadata: name: pvc spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "managed-nfs-storage" resources: requests: storage: 3Gi --- apiVersion: v1 kind: Service metadata: name: redis-headless namespace: redis-cluster labels: app.kubernetes.io/name: redis-cluster spec: ports: - name: redis-6379 protocol: TCP port: 6379 targetPort: 6379 selector: app.kubernetes.io/name: redis-cluster clusterIP: None type: ClusterIP
|
对于这个声明文件你可能会有疑问,上一步建自己的镜像时我说为了安全,能自己控制 redis.conf,但我却没有暴露 redis.conf 反而直接在这个声明文件的 13-21 行之间配置了,其实我是把它声明为了 configmap,修改时觉得还好,就这样部署下来了。
- 接下来在 kubernetes 环境运行就好啦,其中 redis-cluster.yaml 是我自己的 yaml 文件名,注意要先把第 6 步创建出的镜像加载到 k8s 集群的各个节点上!
1
| kubectl apply -f redis-cluster.yaml
|
- 此时系统中运行了 6 个 redis,但它们之间并没有组成集群,需要通过下方指令,注意事项如下:
- redis-cluster-0 创建出来的1个pod的名字(命名规则是文件1第26行 + 副本序号,例如6副本则0-5)
- -n redis-cluster 命名空间,如果文件1的第4行修改了这里也需要修改,注意这段中(-n redis-cluster)出现了2次,其实就是筛选这个命名空间下有特定标签(app.kubernetes.io/name=redis-cluster,文件1的第29行定义)的 pod 共有多少
- -a Fusion@123 文件1 中第 21 行指定的密码
- –cluster-replicas 1 副本数1,6 节点恰好 3 主 3 备
- 后面的 podIP、6379等都不推荐修改,在文件1中通过环境变量等已经配置好了,而且命名空间、容器互相隔离,相互直接使用相同的端口啥的毫无影响
1
| kubectl exec -it redis-cluster-0 -n redis-cluster -- redis-cli -a Fusion@123 --cluster create --cluster-replicas 1 $(kubectl get pods -n redis-cluster -l app.kubernetes.io/name=redis-cluster -o jsonpath='{range.items[*]}{.status.podIP}:6379 {end}')
|
执行后可能会让输入 yes,输入完成后会输出集群创建成功啥的,并展示每个节点分配的槽数(槽是redis-cluster集群中的概念,默认是均分,也有某节点性能特别好需要特殊划分的情况),可以通过创建出来的目录 (/mnt/nfs 之类的)进入后查看 node.conf(文件1第18行配置) 查看
- 那系统中该如何使用呢,以 Java 为例:
1 2 3 4 5 6 7 8 9
| spring: redis: cluster: nodes: redis-cluster-0.redis-headless.redis-cluster.svc.cluster.local:6379,redis-cluster-1.redis-headless.redis-cluster.svc.cluster.local:6379,redis-cluster-2.redis-headless.redis-cluster.svc.cluster.local:6379,redis-cluster-3.redis-headless.redis-cluster.svc.cluster.local:6379,redis-cluster-4.redis-headless.redis-cluster.svc.cluster.local:6379,redis-cluster-5.redis-headless.redis-cluster.svc.cluster.local:6379 lettuce: pool: min-idle: 30 max-idle: 100 password: Fusion@123
|
注意需要部署的 Java 应用和 Redis 在同一 Kubernetes 集群里哦,如果不在则需要通过其它手段(例如 NodePort) 来访问了~
这里只是以部署 Redis-Cluster 为例,其实部署有状态集群如数据库等也是一样的,需要理解的是 Service、StorageClass 等~