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

bash
docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/fnproject/fn-java-fdk:jre11-1.0.211
拉下来后这个 tag 太长了,一般我会改成比较简单的 tag:
bash
docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/fnproject/fn-java-fdk:jre11-1.0.211 jre:11
有时我们内网下载不下镜像,希望在外网下载后导入到内网使用,使用如下命令导出为 tar 文件:
bash
docker save jre:11 -o jre11.tar
注意我用的 jre:11 如果这里使用镜像ID,在内网导入后会丢失 tag 信息,内网导入:
bash
docker load -i jre11.tar
其实部署服务时镜像的选择有很多知识,如果是想要绝对安全的环境(内部 curl、vim等指令均不存在),则使用只包含基础工具的镜像,像 Java 程序运行时其实只依赖 jre,如果使用 jdk 会导致最后的镜像体积很大,当然这也是一种权衡,有了 jdk 后可以定位一些问题。
Docker 运行 Mysql
数据库想必是开发中最常用的工具了,启动指令也非常简单:
bash
docker run --name mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql:5.7
这是个用完即丢的示例,因为这个指令并没有保存 mysql 的数据,非常适合测试,如果想要保存数据,则命令如下:
bash
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
bash
docker run --name redis -d -p 6379:6379 redis:5.0.8
依然是用完即丢的示例,并且没有设置账号和密码,这种都倾向于用完即弃,生产环境千万不要这样
像一些常见的还有 consul、kafka 等...
Kubernetes 部署 Redis-Cluster 集群
Redis-Cluster 和 主从哨兵集群的区别与优劣就不在此比对了,本文是以部署为主。
- 从三方仓库拉取 Redis7 镜像:
bash
docker pull docker.1ms.run/library/redis:7
- 换一个简单的 tag
bash
docker tag docker.1ms.run/library/redis:7 redis:7
- 为了让业务更可控(对于陌生的镜像我们连 redis.conf 在哪都不清楚),基于这个镜像改造我们自己的镜像,首先去 github-redis-release 中找到对应的版本,接着找到里面的 redis.conf 文件
- 在有 docker 环境的机器上组成如下的结构(dockerfile 自行创建):
bash
└── redis
├── dockerfile
└── redis.conf
- 编写 dockerfile
bash
# 基于刚刚下载的镜像
FROM redis:7 as base
# 创建配置目录
RUN mkdir -p /usr/local/etc
# 复制本地的redis.conf到容器中
COPY redis.conf /usr/local/etc/redis.conf
# 可选:设置配置文件权限(如果需要)
RUN chmod 644 /usr/local/etc/redis.conf
# 暴露Redis默认端口
EXPOSE 6379
# 设置启动命令
CMD ["redis-server", "/usr/local/etc/redis.conf"]
将 dockerfile 构建成镜像:
bash
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行等地方
yaml
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 集群的各个节点上!
bash
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中通过环境变量等已经配置好了,而且命名空间、容器互相隔离,相互直接使用相同的端口啥的毫无影响
bash
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 为例:
bash
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 等~