kubernets集群部署流程:
- 网络、主机等规划
- 规划集群网络
- 规划主机集群架构(如节点多机房灾备等)
- 主机系统初始化
- 主机名
- 软件源
- 关闭swap
- 关闭防火墙
- 关闭selinux
- 配置chrony
- 安装依赖软件包
- 加载相关内核模块
- 内核优化
- 配置数据盘
- 签发集群证书
- 签发ca证书
- 签发etcd证书
- 签发admin证书
- 签发apiserver证书
- 签发metrics-server证书
- 签发controller-manager证书
- 签发scheduler证书
- 签发kube-proxy证书
- 部署etcd高可用集群
- etcd集群部署
- etcd数据备份
- apiserver的高可用配置
- haproxy+keepalived
- 部署master节点
- 生成kuberctl kubeconfig,配置kubectl
- 安装配置kube-apiserver
- 安装配置kube-controller-mannager
- 安装配置kube-scheduler
- 部署node节点
- 安装配置docker
- 安装配置kubelet
- 安装配置kube-proxy
- 部署calico
kubernetes集群部署要点:
- 关闭非安全端口,通过–anonymous-auth=false关闭匿名请求,所有通信使用证书认证,防止请求被篡改;通过证书认证和授权策略(x509、token、RBAC)。
- 开启bootstrap token认证,动态创建token,而不是在apiserver中静态配置。
- 使用TLS bootstrap机制自动生成kubelet client和server证书,过期自动轮转(部分集群部署使用admin证书签发kubelet.kubeconfig,这样集群非常不安全)。
- etcd、master证书指定节点IP,限制访问,即便某个节点证书泄漏,在其他机器上也无法使用。
- 网络使用calico ipip
我已经将kubernetes集群安装过程写成ansible-playbook,实现一键部署、扩容kubernets高可用集群(https://github.com/k8sre/k8s_init.git)。playbook包含了系统初始化、签发证书、部署etcd、部署k8s、替换集群证书等。
我们Devops平台对接了Ansible Playbook,可在DevOps平台上操作部署、扩容,配合工单系统,完成全自动化流程。
DevOps平台操作部署、扩容kubernets集群流程:
- 填写申请机器工单
- 选择用途为kubernetes新建或者扩容
- 选择对应角色
- 工单审批通过后,自动申请机器
- 调用Ansible Playbook开始进行系统初始化
- 调用Ansible Playbook开始进行证书签发
- 调用Ansible Playbook开始进行etcd集群部署
- 调用Ansible Playbook开始进行节点扩容
同时也可以使用以下命令进行操作:
通过以下命令即可将一批刚刚安装好系统、配置好网络的机器部署为一套生产可用的k8s集群:
ansible-playbook k8s.yml -i inventory
扩容master节点
ansible-playbook k8s.yml -i inventory -t init -l master
ansible-playbook k8s.yml -i inventory -t cert,install_master
扩容node节点
ansible-playbook k8s.yml -i inventory -t init -l node
ansible-playbook k8s.yml -i inventory -t cert,install_node
通过本playbook部署集群,可以在很短的时间内完成集群扩容。生产上使用本Playbook最高一次性扩容过50个node节点。
因为时间关系,这里先简单介绍下kubelet TLS Bootstrapping和etcd的自动备份,其他部分就不再进一步讲解,具体详细部署大家可参考(https://github.com/k8sre/docs/blob/master/kubernetes/kubernetes高可用集群之二进制部署.md)
Kubelet TLS Bootstrapping
在Kubernetes集群中,工作节点上的组件(kubelet和kube-proxy)需要与Kubernetes主组件通信,特别是kube-apiserver。为了确保通信保持私密,不受干扰,并确保群集的每个组件与另一个受信任组件通信,我们强烈建议在节点上使用客户端TLS证书。
引导这些组件的正常过程,特别是需要证书的工作节点,因此它们可以与kube-apiserver安全地通信,这可能是一个具有挑战性的过程,因为它通常超出了Kubernetes的范围,需要大量的额外工作。反过来,这可能会使初始化或扩展集群变得具有挑战性。
为了简化流程,从版本1.4开始,Kubernetes引入了证书请求和签名API以简化流程。
kubernetes开启TLS认证后,每个node节点的kubelet都要使用由kube-apiserver的ca签发证书,才能与kube-apiserver进行通信,如果接入比较多的节点,为每个节点生成证书不太现实,Bootstrapping就是让kubelet先使用一个低权限用户连接到kube-apiserver,然后由kube-controller-manager为kubelet动态签发证书。再配合RBAC指定用户拥有访问哪些API的权限。
CSR 请求
kubelet启动后会发起CSR请求,kube-apiserver 收到 CSR 请求后,对其中的 Token 进行认证,认证通过后将请求的 user 设置为 system:bootstrap:<Token ID>
,group 设置为 system:bootstrappers
,这一过程称为 Bootstrap Token Auth。
kubelet发起的 CSR 请求都是由kube-controller-manager来做实际签发的,对于kube-controller-manager来说,TLS bootstrapping 下 kubelet 发起的 CSR 请求大致分为以下三种
- nodeclient: kubelet 以
O=system:nodes
和CN=system:node:(node name)
形式发起的 CSR 请求 - selfnodeclient: kubelet client renew 自己的证书发起的 CSR 请求(与上一个证书就有相同的 O 和 CN)
- selfnodeserver: kubelet server renew 自己的证书发起的 CSR 请求
使用 Bootstrap Token 时整个启动引导过程:
- 配置apiserver使用
Bootstrap Token Secret
,替换以前使用token.csv
文件 - 在集群内创建首次 TLS Bootstrap 申请证书的 ClusterRole、后续 renew Kubelet client/server 的 ClusterRole,以及其相关对应的 ClusterRoleBinding;并绑定到对应的组或用户
- 配置controller-manager,使其可以自动签发相关证书和自动清理过期的TLS Bootstrapping Token
- 生成特定的包含 TLS Bootstrapping Token 的
bootstrap.kubeconfig
以供 kubelet 启动时使用 - 配置kubelet,使其首次启动加载
bootstrap.kubeconfig
并使用其中的 TLS Bootstrapping Token 完成首次证书申请 - controller-manager签发证书,并生成kubelet.kubeconfig,kubelet自动重载完成引导流程
- 后续 kubelet 自动 renew 相关证书
- 集群搭建成功后立即清除
Bootstrap Token Secret
,或等待 Controller Manager 待其过期后删除,以防止被恶意利用
下面讲一下etcd备份
ETCD备份
Etcd是Kubernetes集群中的一个十分重要的组件,用于保存集群所有的网络配置和对象的状态信息。一旦出现问题或者数据丢失,影响将无法估计。所以,我们一定要做好etcd的数据备份。
这里利用kubernetes的cronjob对etcd进行备份,并挂载外部存储,将数据备份在外部存储上。
以下是使用cronjob备份的yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: etcd-backup
spec:
schedule: "10 1 * * *"
concurrencyPolicy: Allow
failedJobsHistoryLimit: 3
successfulJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
containers:
- name: etcd-backup
imagePullPolicy: IfNotPresent
image: k8sre/etcd:v3.3.15
env:
- name: ENDPOINTS
value: "https://172.16.70.131:2379,https://172.16.70.132:2379,https://172.16.70.133:2379"
command:
- /bin/bash
- -c
- |
set -ex
ETCDCTL_API=3 /opt/etcd/etcdctl \
--endpoints=$ENDPOINTS \
--cacert=/certs/ca.pem \
--cert=/certs/etcd.pem \
--key=/certs/etcd.key \
snapshot save /data/backup/snapshot-$(date +"%Y%m%d").db
volumeMounts:
- name: etcd-backup
mountPath: /data/backup
- name: etcd-certs
mountPath: /certs
restartPolicy: OnFailure
volumes:
- name: etcd-backup
persistentVolumeClaim:
claimName: etcd-backup
- name: etcd-certs
secret:
secretName: etcd-certs
下面来讲一下,我们的CICD是如何实现的。
先给大家看下我们大概的架构图:
我们业务主要的开发语言包含Java、PHP、Golang、NodeJs以及少部分Python,涉及开发语言较广,CI、CD需求各种各样等原因,我们选择使用Jenkins Pipeline来完成CI、CD的相关工作,全部逻辑通过Groovy语言编写。并通过Jenkins Agent的方式适配公司所有开发语言代码的CICD。同时支持虚拟机和k8s部署。
Jenkins Master及所有agent都部署在kubernetes集群,并使用pvc挂载一块极速nas作为数据同步。下面给大家看下我们部分部署yaml:
jenkins-master.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-master
namespace: jenkins
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: jenkins-master
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: jenkins-master
namespace: jenkins
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: jenkins-master
namespace: jenkins
spec:
serviceName: "jenkins-master"
replicas: 1
selector:
matchLabels:
app: jenkins-master
template:
metadata:
labels:
app: jenkins-master
spec:
serviceAccount: jenkins-master
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: app
operator: In
values:
- jenkins
containers:
- name: jenkins
image: k8sre/jenkins:master
imagePullPolicy: Always
securityContext:
runAsUser: 0
privileged: true
ports:
- containerPort: 8080
name: http
protocol: TCP
- containerPort: 50000
name: agentport
protocol: TCP
resources:
limits:
cpu: 2
memory: 4Gi
requests:
cpu: 2
memory: 4Gi
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 10
timeoutSeconds: 3
periodSeconds: 5
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 10
timeoutSeconds: 3
periodSeconds: 5
volumeMounts:
- name: jenkins-data
mountPath: /data/jenkins
- name: sshkey
mountPath: /root/.ssh/id_rsa
subPath: id_rsa
readOnly: true
- name: jenkins-docker
mountPath: /var/run/docker.sock
- name: jenkins-maven
mountPath: /usr/share/java/maven-3/conf/settings.xml
subPath: settings.xml
readOnly: true
volumes:
- name: sshkey
configMap:
defaultMode: 0600
name: sshkey
- name: jenkins-docker
hostPath:
path: /var/run/docker.sock
type: Socket
- name: jenkins-maven
configMap:
name: jenkins-maven
- name: jenkins-data
persistentVolumeClaim:
claimName: jenkins
---
apiVersion: v1
kind: Service
metadata:
name: jenkins-master
namespace: jenkins
spec:
ports:
- name: web
port: 8080
protocol: TCP
targetPort: 8080
- name: agent
port: 50000
protocol: TCP
targetPort: 50000
selector:
app: jenkins-master
agent-go.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: agent-go
namespace: jenkins
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: agent-go
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: agent-go
namespace: jenkins
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: agent-go
namespace: jenkins
spec:
serviceName: "agent-go"
replicas: 1
selector:
matchLabels:
app: agent-go
template:
metadata:
labels:
app: agent-go
spec:
serviceAccount: agent-go
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: role
operator: In
values:
- jenkins
containers:
- name: jenkins
image: k8sre/jenkins:go
imagePullPolicy: Always
securityContext:
runAsUser: 600
privileged: true
env:
- name: "ENV_NAME"
value: "go"
- name: "JKS_SECRET"
value: "xxxxxxxxxxxxxx"
- name: "JKS_DOMAINNAME"
value: "https://ci.k8sre.com"
- name: "JKS_ARGS"
value: "-Xmx4g -Xms4g"
- name: "JKS_DIR"
value: "/data/jenkins"
volumeMounts:
- mountPath: /data/jenkins
name: jenkins-data
volumes:
- name: jenkins-data
persistentVolumeClaim:
claimName: jenkins
下图是我们目前jenkins在使用的节点:
以下是CICD的流程图:
我们CICD的流程:
- —> 在DevOps平台创建项目(Devops平台提供项目管理、配置管理、JOB处理、日志查看、监控信息、k8s容器web terminal等管理功能,CICD后续流程全部为Jenkins处理)
- —>选择是容器部署or虚机部署
- —>按照提示填写相关配置(包含开发语言、GIT仓库、编译命令、域名、容器内可写目录、运行命令、运行端口、资源限制、健康检查等)
- —>保存配置
- —>触发JOB
- —>发布前检查/拉取项目配置(同时对项目配置进行校验是否符合规范)
- —>拉取业务代码
- —>漏洞扫描
- —>编译构建
- —>根据配置选择对应语言的编译节点
- —>判断虚机还是容器部署,选择不同的部署流程
- —> 容器部署会根据项目配置生成Dockerfile并构建上传镜像(适配所有业务Dockerfile自动生成,并支持自定义)
- —>选择环境(prd or pre or qa or dev )
- —>helm使用模版替换项目配置进行部署(helm chart模版适配了所有业务需求,并支持自定义。默认部署deployment、service、ingress、hpa)
- —>部署结束后发送部署详情通知
jenkins pipeline测试环境阶段视图示例:
更多技术文章请关注公众号:架构师Plus,
扫码添加