consul server在机器上的部署已经写完了,但是dba还是希望部署在k8s上,昨天搜了下相关文章扣出来部分步骤自己验证实现了。
先说下需求,三台物理机单独跑mysql主从mha,利用consul client的服务注册+ttl向consul server注册成域名,然后配置k8s内部的coredns把域名 *.service.consul
转发到consul server。架构为下图
Physical server +---------------------+ K8S Cluster | | +--------------------------------+ |mysql + consul client+-----------+ | +---------------------+ | | | | | | +-------------+ | | +---------------------+ | | | |consul server| | | Physical server | | | D +------+------+ | | +---------------------+ | | | e | | | | | | | | p +------+------+ | | |mysql + consul client+----------->------>+ | l |consul server| | | | | | | | o +------+------+ | | +---------------------+ | | | y | | | Physical server | | | +------+------+ | | +---------------------+ | | | |consul server| | | | | | | | +-------------+ | | |mysql + consul client+-----------+ | +---------------------+ | | | +--------------------------------+ +---------------------+
consul有k8s client的相关代码,赋予RBAC后能够用selector能够自动join到其他成员
"retry_join": [ "provider=k8s label_selector=/"app=consul,component=server/"" ],
当然其实市面上还有其他的方案的,为啥我不用
因为这里我是consul server跑k8s里,consul client在物理机上,通信我要配置成tls,tls我是secret导入的,cm写配置文件。挂载到pod里后因为官方docker镜像的 entrypoint.sh
里chown了配置文件目录,而挂载到pod内部是只读的,pod起来后执行到chown那就报错退出了。所以改造了下官方的entrypoint脚本
FROM consul:1.6.1 LABEL maintainer="zhangguanzhang <zhangguanzhang@qq.com>" ARG TZ=Asia/Shanghai RUN set -eux && / ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && / echo ${TZ} > /etc/timezone COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
docker-entrypoint.sh
见我github https://raw.githubusercontent.com/zhangguanzhang/Dockerfile/master/consul-k8s-dns/docker-entrypoint.sh
Dockerfile我是打算直接改官方的而不是FROM镜像,但是gpg key出问题所以我写成上面那样构建的镜像。见issue https://github.com/hashicorp/docker-consul/issues/137
自己构建镜像的话 docker-entrypoint.sh
记得加执行权限,我构建的镜像推送到dockerhub上可以直接拉取
证书操作都是在容器里生成的
mkdir ssl docker run --rm -ti --entrypoint sh --workdir /root -v $PWD/ssl:/root/ zhangguanzhang/consul-k8s-dns
我们先进入容器
为了简单起见,这里我使用Consul命令行的的内置TLS功能来创建基本的CA。您只需为数据中心创建一个CA。您应该在用于创建CA的同一服务器上生成所有证书。
ca默认五年,其他的证书默认1年,这里需要带参数 -days=
设置长点的日期
consul tls ca create -days=36500 ==> Saved consul-agent-ca.pem ==> Saved consul-agent-ca-key.pem
这里数据中心默认名字为dc1,其他的自行选项赋值。在创建CA的同一台服务器上重复此过程,直到每台服务器都有一个单独的证书。该命令可以反复调用,它将自动增加证书和密钥号。您将需要将证书分发到服务器。
严格来说每个server单独使用,也就是说假如三个server,下面命令应该执行三次。但是我们仅仅当无状态服务使用,如果每个server单独一套证书就不能放secret里,毕竟文件名不一样不能对照到pod上,这里我们仅仅生成一份
consul tls cert create -server -dc=dc1 -days=36500 ==> WARNING: Server Certificates grants authority to become a server and access all state in the cluster including root keys and all ACL tokens. Do not distribute them to production hosts that are not server nodes. Store them as securely as CA keys. ==> Using consul-agent-ca.pem and consul-agent-ca-key.pem ==> Saved dc1-server-consul-0.pem ==> Saved dc1-server-consul-0-key.pem
在Consul 1.5.2中,您可以使用替代过程来自动将证书分发给客户端。要启用此新功能,请设置 auto_encrypt
。
您可以继续使用生成证书 consul tls cert create -client
并手动分发证书。对于需要高度保护的数据中心,仍然需要现有的工作流程。
如果您正在运行Consul 1.5.1或更早版本,则需要使用来为每个客户端创建单独的证书 consul tls cert create -client
。客户端证书也由您的CA签名,但是它们没有特殊性 Subject Alternative Name
,这意味着如果 verify_server_hostname
启用,则它们不能作为server角色启动。
这里我是高于1.5.2的,不需要为每个客户端创建证书,客户端只需要拥有 consul-agent-ca.pem
这个ca下,会自动从server获取证书存在内存中,并且不会持久保存。但是我测试了并没有成功,还是生成了证书
$ consul tls cert create -client -dc=dc1 -days=36500 ==> Using consul-agent-ca.pem and consul-agent-ca-key.pem ==> Saved dc1-client-consul-0.pem ==> Saved dc1-client-consul-0-key.pem $ consul tls cert create -client -dc=dc1 -days=36500 ==> Using consul-agent-ca.pem and consul-agent-ca-key.pem ==> Saved dc1-client-consul-1.pem ==> Saved dc1-client-consul-1-key.pem
客户端实体服务跑,可以单独去生成而不是共用一套
$ consul tls cert create -cli -dc=dc1 -days=36500 ==> Using consul-agent-ca.pem and consul-agent-ca-key.pem ==> Saved dc1-cli-consul-0.pem ==> Saved dc1-cli-consul-0-key.pem
创建完证书ctrl+d退出容器
consul可以跨数据中心,wan相关配置(hostPort模拟)我没有测试成功,而且考虑到高可用,基本是每台机器一台。所以我用了硬性互斥+hostNetwork,关闭serfwan端口。
nodeSelector
记得自己去打label固定住
apply之前我们先把证书导入成secret
cd ssl kubectl create secret generic consul / --from-file=consul-agent-ca.pem / --from-file=dc1-server-consul-0.pem / --from-file=dc1-server-consul-0-key.pem / --from-file=dc1-cli-consul-0.pem / --from-file=dc1-cli-consul-0-key.pem
另外要注意,因为每次pod重启基本相当于node-id变了,参数 leave_on_terminate
应该false,见 https://github.com/hashicorp/consul/issues/6672
和 https://github.com/hashicorp/consul/issues/3938
所以preStop的部分没啥用,忽略即可
因为coredns的转发需要ip或者文件,所以我们得把consul-server的svc的clusterIP给固定住(记住得在svc的cidr内选一个未使用的ip),或者干脆不创建svc直接使用hostIP,这里按照svc实例
apiVersion: apps/v1 kind: Deployment metadata: name: consul-server spec: selector: matchLabels: app: consul component: server replicas: 3 template: metadata: labels: app: consul component: server spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - consul topologyKey: kubernetes.io/hostname serviceAccountName: consul-server nodeSelector: master: "true" terminationGracePeriodSeconds: 7 hostNetwork: true securityContext: fsGroup: 1000 containers: - name: consul image: "zhangguanzhang/consul-k8s-dns" env: - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP - name: NODE valueFrom: fieldRef: fieldPath: spec.nodeName - name: CONSUL_HTTP_ADDR value: https://localhost:8501 - name: CONSUL_CACERT value: /consul/config/ssl/consul-agent-ca.pem - name: CONSUL_CLIENT_CERT value: /consul/config/ssl/dc1-cli-consul-0.pem - name: CONSUL_CLIENT_KEY value: /consul/config/ssl/dc1-cli-consul-0-key.pem # - name: RETRY_JOIN # value: 172.19.0.5,172.19.0.6,172.19.0.7 args: - agent - -advertise=$(POD_IP) - -node=$(NODE) volumeMounts: - name: data mountPath: /consul/data - name: config mountPath: /consul/config/ - name: tls mountPath: /consul/config/ssl/ lifecycle: preStop: exec: command: - consul leave readinessProbe: exec: command: - consul - members failureThreshold: 2 initialDelaySeconds: 10 periodSeconds: 5 successThreshold: 1 timeoutSeconds: 3 ports: - containerPort: 8300 name: server - containerPort: 8301 name: serflan # - containerPort: 8302 # name: serfwan - containerPort: 8400 name: alt-port - containerPort: 8501 name: https - containerPort: 8600 name: dns-udp protocol: UDP - containerPort: 8600 name: dns-tcp protocol: TCP volumes: - name: config configMap: name: consul-server - name: tls secret: secretName: consul - name: data emptyDir: {} --- apiVersion: v1 kind: Service metadata: name: consul-server namespace: default labels: app: consul spec: clusterIP: 10.96.0.11 ports: - name: https port: 8501 targetPort: https - name: serflan-tcp protocol: "TCP" port: 8301 targetPort: 8301 - name: serflan-udp protocol: "UDP" port: 8301 targetPort: 8301 # - name: serfwan-tcp # protocol: "TCP" # port: 8302 # targetPort: 8302 # - name: serfwan-udp # protocol: "UDP" # port: 8302 # targetPort: 8302 - name: server port: 8300 targetPort: 8300 - name: dns-tcp protocol: "TCP" port: 8600 targetPort: dns-tcp - name: dns-udp protocol: "UDP" port: 8600 targetPort: dns-udp selector: app: consul component: server --- apiVersion: v1 kind: ConfigMap metadata: name: consul-server namespace: default labels: app: consul role: server data: server.json: |- { "client_addr": "0.0.0.0", "datacenter": "dc1", "bootstrap_expect": 3, "domain": "consul", "skip_leave_on_interrupt": true, "leave_on_terminate" : false, "log_level": "INFO", "retry_join": [ "provider=k8s label_selector=/"app=consul,component=server/"" ], "retry_interval": "2s", "verify_incoming": true, "verify_outgoing": true, "verify_server_hostname": true, "ca_file": "/consul/config/ssl/consul-agent-ca.pem", "cert_file": "/consul/config/ssl/dc1-server-consul-0.pem", "key_file": "/consul/config/ssl/dc1-server-consul-0-key.pem", "ports": { "http": -1, "serf_wan": -1, "https": 8501 }, "server": true, "ui": false } --- apiVersion: v1 kind: ServiceAccount metadata: name: consul-server labels: app: consul --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: consul labels: app: consul rules: - apiGroups: [""] resources: - pods verbs: - get - list --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: consul roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: consul subjects: - kind: ServiceAccount name: consul-server namespace: default
apply后
[root@k8s-m1 consul-tls-ansible]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES consul-server-7879498d78-8qjbs 1/1 Running 0 2m34s 172.19.0.6 172.19.0.6 <none> <none> consul-server-7879498d78-drm7n 1/1 Running 0 2m34s 172.19.0.7 172.19.0.7 <none> <none> consul-server-7879498d78-zjc6z 1/1 Running 0 2m34s 172.19.0.5 172.19.0.5 <none> <none>
client是实体服务,用我的ansible部署的,client配置服务注册
{ "services": [ { "name": "r-3306-mysql", "tags": [ "slave-3306" ], "address": "172.16.0.2", "port": 3306, "checks": [ { "args": ["echo"], //实际应该写健康检查脚本,并去掉此处注释 "interval": "5s" } ] } ] }
client的consul reload后看到服务注册上去
2019/10/24 13:32:12 [INFO] agent: (LAN) joined: 3 2019/10/24 13:32:12 [INFO] agent: Join LAN completed. Synced with 3 initial agents 2019/10/24 13:32:12 [INFO] agent: Synced service "r-3306-mysql" 2019/10/24 13:32:21 [INFO] agent: Synced check "service:r-3306-mysql"
查看members
[root@k8s-m1 consul-tls-ansible]# kubectl exec consul-server-7879498d78-8qjbs consul members Node Address Status Type Build Protocol DC Segment 172.19.0.5 172.19.0.5:8301 alive server 1.6.1 2 dc1 <all> 172.19.0.6 172.19.0.6:8301 alive server 1.6.1 2 dc1 <all> 172.19.0.7 172.19.0.7:8301 alive server 1.6.1 2 dc1 <all> 172.19.0.13 172.19.0.13:8301 alive client 1.6.1 2 dc1 <default> [root@k8s-m1 consul-tls-ansible]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE consul-server ClusterIP 10.96.0.11 <none> 8501/TCP,8301/TCP,8301/UDP,8300/TCP,8600/TCP,8600/UDP 2m48s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d22h
直接使用svc来测试域名解析
[root@k8s-m1 consul-tls-ansible]# dig -p 8600 @10.96.0.11 r-3306-mysql.service.consul +short 172.16.0.2
配置coredns转发,coredns的configmap添加下面内容,也可以添加上client的ip
... service.consul:53 { errors cache 30 forward . 10.96.0.11:8600 172.19.0.13:8600 }
跑一个指定版本带解析的工具pod
$ cat<<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: busybox namespace: default spec: containers: - name: busybox image: busybox:1.28 command: - sleep - "3600" imagePullPolicy: IfNotPresent restartPolicy: Always EOF
测试解析,不影响集群的内部解析
$ kubectl exec -ti busybox -- nslookup r-3306-mysql.service.consul Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: r-3306-mysql.service.consul Address 1: 172.16.0.2 $ kubectl exec -ti busybox -- nslookup kubernetes Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: kubernetes Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local