Kubernetes(K8S)容器集群管理环境完整部署详细教程-上篇
kubernetes(通常称为"k8s")是google开源的容器集群管理系统。其设计目标是在主机集群之间提供一个能够自动化部署、可拓展、应用容器可运营的平台。kubernetes通常结合docker容器工具工作,并且整合多个运行着docker容器的主机集群,kubernetes不仅仅支持docker,还支持rocket,这是另一种容器技术。kubernetes是一个用于容器集群的自动化部署、扩容以及运维的开源平台。
本文系列:
kubernetes(k8s)容器集群管理环境完整部署详细教程-上篇
通过kubernetes, 可以快速有效地响应用户需求:
-> 快速而有预期地部署应用;
-> 极速地扩展你的应用;
-> 无缝对接新的应用功能;
-> 节省资源,优化硬件资源的使用;
kubernetes功能特性:
-> 自动化容器部署与复制
-> 随时扩展或收缩容器规模
-> 组织容器成组,提供容器间的负载均衡
-> 快速更新及回滚容器版本
-> 提供弹性伸缩,如果某个容器失效就进行替换
kubernetes重要组件:
1)master组件
master节点上面主要由四个模块组成:apiserver、scheduler、controller manager、etcd
-> apiserver: 负责对外提供restful的kubernetes api服务,它是系统管理指令的统一入口,任何对资源进行增删改查的操作都要交给apiserver处理后再提交给etcd。kubectl(k8s提供的客户端工具,该工具内部就是对kubernetes api的调用)是直接和apiserver交互的。
-> schedule: 它的职责很明确,就是负责调度pod到合适的node上。如果把scheduler看成一个黑匣子,那么它的输入是pod和由多个node组成的列表,输出是pod和一个node的绑定,即将这个pod部署到这个node上。kubernetes目前提供了调度算法,但是同样也保留了接口,用户可以根据自己的需求定义自己的调度算法。
-> controller manager: 如果说apiserver做的是“前台”的工作的话,那controller manager就是负责“后台”的。每个资源一般都对应有一个控制器,而controller manager就是负责管理这些控制器的。比如我们通过apiserver创建一个pod,当这个pod创建成功后,apiserver的任务就算完成了。而后面保证pod的状态始终和我们预期的一样的重任就由controller manager去保证了。
-> etcd: 它是一个高可用的键值存储系统,kubernetes使用它来存储各个资源的状态,从而实现了restful的api。
2)node组件
每个node节点主要由三个模块组成:kubelet、kube-proxy、runtime。
runtime。runtime指的是容器运行环境,目前kubernetes支持docker和rkt两种容器。
-> kubelet:kubelet是master在每个node节点上面的agent,是node节点上面最重要的模块,它负责维护和管理该node上面的所有容器,但是如果容器不是通过kubernetes创建的,它并不会管理。本质上,它负责使pod得运行状态与期望的状态一致。
-> kube-proxy:该模块实现了kubernetes中的服务发现和反向代理功能。反向代理方面:kube-proxy支持tcp和udp连接转发,默认基于round robin算法将客户端流量转发到与service对应的一组后端pod。服务发现方面,kube-proxy使用etcd的watch机制,监控集群中service和endpoint对象数据的动态变化,并且维护一个service到endpoint的映射关系,从而保证了后端pod的ip变化不会对访问者造成影响。另外kube-proxy还支持session affinity。
3)pod
pod是k8s进行资源调度的最小单位,每个pod中运行着一个或多个密切相关的业务容器,这些业务容器共享这个pause容器的ip和volume,我们以这个不易死亡的pause容器作为pod的根容器,以它的状态表示整个容器组的状态。一个pod一旦被创建就会放到etcd中存储,然后由master调度到一个node绑定,由这个node上的kubelet进行实例化。每个pod会被分配一个单独的pod ip,pod ip + containerport 组成了一个endpoint。
4)service
service其功能使应用暴露,pods 是有生命周期的,也有独立的 ip 地址,随着 pods 的创建与销毁,一个必不可少的工作就是保证各个应用能够感知这种变化。这就要提到 service 了,service 是 yaml 或 json 定义的由 pods 通过某种策略的逻辑组合。更重要的是,pods 的独立 ip 需要通过 service 暴露到网络中。
k8s集群可以帮助培育出一个组件及工具的生态,帮助减轻在公有云及私有云上运行应用的负担。
搭建kubernetes集群环境有以下三种方式:
1. minikube安装方式
minikube是一个工具,可以在本地快速运行一个单点的kubernetes,尝试kubernetes或日常开发的用户使用。但是这种方式仅可用于学习和测试部署,不能用于生产环境。
2. kubeadm安装方式
kubeadm是一个kubernetes官方提供的快速安装和初始化拥有最佳实践(best practice)的kubernetes集群的工具,提供kubeadm init和kubeadm join,用于快速部署kubernetes集群。目前kubeadm还处于beta 和alpha状态,不推荐用在生产环境,但是可以通过学习这种部署方法来体会一些官方推荐的kubernetes最佳实践的设计和思想。
kubeadm的目标是提供一个最小可用的可以通过kubernetes一致性测试的集群,所以并不会安装任何除此之外的非必须的addon。kubeadm默认情况下并不会安装一个网络解决方案,所以用kubeadm安装完之后,需要自己来安装一个网络的插件。所以说,目前的kubeadm是不能用于生产环境的
3. 二进制包安装方式(生产部署的推荐方式)
从官方下载发行版的二进制包,手动部署每个组件,组成kubernetes集群,这种方式符合企业生产环境标准的kubernetes集群环境的安装,可用于生产方式部署。
一、基础信息
使用kubernetes1.14.2,所有节点机操作系统是centos7.5。本文档部署中所需kubernetes相关安装包和镜像可提前在fq服务器上下载,然后同步到k8s部署机器上。具体信息如下:
ip地址 | 主机名 | 角色 |
172.16.60.241 | k8s-master01 | 主节点1、etc节点1 |
172.16.60.242 | k8s-master02 | 主节点2、etc节点2 |
172.16.60.243 | k8s-master03 | 主节点3、etc节点3 |
172.16.60.244 | k8s-node01 | 工作节点1 |
172.16.60.245 | k8s-node02 | 工作节点2 |
172.16.60.246 | k8s-node03 | 工作节点3 |
172.16.60.247 | k8s-ha01 | nginx节点1、harbor节点1 |
172.16.60.248 | k8s-ha02 | nginx节点2、harbor节点2 |
本套kubernetes集群环境版本
- kubernetes 1.14.2
- docker 18.09.6-ce
- etcd 3.3.13
- flanneld 0.11.0
插件:
- coredns
- dashboard
- metrics-server
镜像仓库:
- harbor(两个仓库相互同步,对外提供统一入口vip地址)
主要配置策略
kube-apiserver高可用(nginx负载层):
- 使用nginx+keepalived实现高可用, vip1:172.16.60.250;
- 关闭非安全端口 8080 和匿名访问;
- 在安全端口 6443 接收 https 请求;
- 严格的认证和授权策略 (x509、token、rbac);
- 开启 bootstrap token 认证,支持 kubelet tls bootstrapping;
- 使用 https 访问 kubelet、etcd,加密通信;
kube-controller-manager高可用:
- 3节点高可用;
- 关闭非安全端口,在安全端口 10252 接收 https 请求;
- 使用 kubeconfig 访问 apiserver 的安全端口;
- 自动 approve kubelet 证书签名请求 (csr),证书过期后自动轮转;
- 各controller 使用自己的 serviceaccount 访问 apiserver;
kube-scheduler高可用:
- 3节点高可用;
- 使用 kubeconfig 访问 apiserver 的安全端口;
kubelet:
- 使用 kubeadm 动态创建 bootstrap token,而不是在 apiserver 中静态配置;
- 使用tls bootstrap机制自动生成 client 和 server 证书,过期后自动轮转;
- 在 kubeletconfiguration 类型的 json 文件配置主要参数;
- 关闭只读端口,在安全端口 10250 接收 https 请求,对请求进行认证和授权,拒绝匿名访问和非授权访问;
- 使用 kubeconfig 访问 apiserver 的安全端口;
kube-proxy:
- 使用kubeconfig 访问 apiserver 的安全端口;
- 在kubeproxyconfiguration 类型的 json 文件配置主要参数;
- 使用ipvs代理模式;
集群插件:
- dns:使用功能、性能更好的 coredns;
- dashboard:支持登录认证;
- metric:metrics-server,使用 https 访问 kubelet 安全端口;
- log:elasticsearch、fluend、kibana;
- registry 镜像库:harbor私有仓库,两个节点相互同步;
kubernetes集群部署中生成的证书文件如下:
ca-key.pem 根私钥(controller-manager配置的时候,跟上--service-account-private-key-file)
ca.pem 根证书(apiserver配置的时候,跟上--service-account-key-file)
kubernetes-key.pem 集群私钥
kubernetes.pem 集群证书
kube-proxy.pem proxy证书-node节点进行认证
kube-proxy-key.pem proxy私钥-node节点进行认证
admin.pem 管理员证书-主要用于kubectl认证
admin-key.pem 管理员私钥-主要用于kubectl认证
tls作用:
就是对通讯加密,防止中间人窃听;同时如果证书不信任的话根本就无法与 apiserver 建立连接,更不用提有没有权限向 apiserver 请求指定内容。
rbac作用:
rbac 中规定了一个用户或者用户组(subject)具有请求哪些 api 的权限;在配合 tls 加密的时候,实际上 apiserver 读取客户端证书的 cn 字段作为用户名,读取 o 字段作为用户组。
总之想要与apiserver通讯就必须采用由apiserver ca签发的证书,这样才能形成信任关系,建立tls连接;另外可通过证书的cn、o字段来提供rbac所需用户与用户组。
kubernetes集群会默认开启rabc(角色访问控制机制),这里提前了解几个重要概念:
- drbc
k8s 1.6引进,是让用户能够访问k8s api资源的授权方式(不授权就没有资格访问k8s的资源)
- 用户
k8s有两种用户:user 和 service account。其中,user给用户使用,service account给进程使用,让进程有相关权限。如dashboard就是一个进程,可以创建一个service account给它使用。
- 角色
role是一系列权限的集合,例如一个role可包含读取和列出pod的权限(clusterrole和role类似,其权限范围是整个集群)
- 角色绑定
rolebinding把角色映射到用户,从而让这些用户拥有该角色的权限(clusterrolebinding和rolebinding类似,可让用户拥有clusterole的权限)
- secret
secret是一个包含少量敏感信息如密码,令牌或密钥的对象。把这些信息保存在secret对象中,可以在这些信息被使用时加以控制,并可以减低信息泄露的风险。
二、环境初始化准备
kubernetes集群部署过程均需要使用root账号操作,下面初始化操作在k8s的master和node节点上操作。
这里先以k8s-master01节点为例,其他节点类似操作。 1)主机名修改 [root@k8s-master01 ~]# hostnamectl set-hostname k8s-master01 如果dns不支持解析主机名称,则需要修改/etc/hosts文件,添加主机名和ip的对应关系: [root@k8s-master01 ~]# cat >> /etc/hosts <<eof 172.16.60.241 k8s-master01 172.16.60.242 k8s-master02 172.16.60.243 k8s-master03 172.16.60.241 k8s-etcd01 172.16.60.242 k8s-etcd02 172.16.60.243 k8s-etcd03 172.16.60.244 k8s-node01 172.16.60.245 k8s-node02 172.16.60.246 k8s-node03 eof 2) 添加docker账户 [root@k8s-master01 ~]# useradd -m docker 3) 无密码ssh信任关系 本篇部署文档有很有操作都是在k8s-master01节点上执行,然后远程分发文件到其他节点机器上并远程执行命令,所以需要添加该节点到其它节点的ssh信任关系。 [root@k8s-master01 ~]# ssh-keygen -t rsa [root@k8s-master01 ~]# cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys [root@k8s-master01 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub -p22 root@k8s-master01 [root@k8s-master01 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub -p22 root@k8s-master02 [root@k8s-master01 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub -p22 root@k8s-master03 [root@k8s-master01 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub -p22 root@k8s-node01 [root@k8s-master01 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub -p22 root@k8s-node02 [root@k8s-master01 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub -p22 root@k8s-node03 以上信任关系设置后,最好手动验证下本节点登陆到其他节点的ssh无密码信任关系 4) 更新path变量,将可执行文件目录添加到path环境变量中 将可执行文件目录添加到path环境变量中 [root@k8s-master01 ~]# echo 'path=/opt/k8s/bin:$path' >>/root/.bashrc [root@k8s-master01 ~]# source /root/.bashrc 5) 安装依赖包 [root@k8s-master01 ~]# yum install -y epel-release [root@k8s-master01 ~]# yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget lsof telnet 关闭无关的服务 [root@k8s-master01 ~]# systemctl stop postfix && systemctl disable postfix 6)关闭防火墙 在每台机器上关闭防火墙,清理防火墙规则,设置默认转发策略: [root@k8s-master01 ~]# systemctl stop firewalld [root@k8s-master01 ~]# systemctl disable firewalld [root@k8s-master01 ~]# iptables -f && iptables -x && iptables -f -t nat && iptables -x -t nat [root@k8s-master01 ~]# iptables -p forward accept [root@k8s-master01 ~]# firewall-cmd --state not running 7) 关闭selinux 关闭selinux,否则后续k8s挂载目录时可能报错 permission denied: [root@k8s-master01 ~]# setenforce 0 [root@k8s-master01 ~]# sed -i 's/^selinux=.*/selinux=disabled/' /etc/selinux/config 8) 关闭swap分区 如果开启了swap分区,kubelet会启动失败(可以通过将参数 --fail-swap-on 设置为false来忽略swap on),故需要在每个node节点机器上关闭swap分区。 这里索性将所有节点的swap分区都关闭,同时注释/etc/fstab中相应的条目,防止开机自动挂载swap分区: [root@k8s-master01 ~]# swapoff -a [root@k8s-master01 ~]# sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab 9) 关闭dnsmasq linux系统开启了dnsmasq后(如 gui 环境),将系统dns server设置为 127.0.0.1,这会导致docker容器无法解析域名,需要关闭它 (centos7系统可能默认没有安装这个服务) [root@k8s-node01 ~]# systemctl stop dnsmasq [root@k8s-node01 ~]# systemctl disable dnsmasq 10)加载内核模块 [root@k8s-master01 ~]# modprobe ip_vs_rr [root@k8s-master01 ~]# modprobe br_netfilter 11)优化内核参数 [root@k8s-master01 ~]# cat > kubernetes.conf <<eof net.bridge.bridge-nf-call-iptables=1 net.bridge.bridge-nf-call-ip6tables=1 net.ipv4.ip_forward=1 net.ipv4.tcp_tw_recycle=0 #由于tcp_tw_recycle与kubernetes的nat冲突,必须关闭!否则会导致服务不通。 vm.swappiness=0 #禁止使用 swap 空间,只有当系统 oom 时才允许使用它 vm.overcommit_memory=1 #不检查物理内存是否够用 vm.panic_on_oom=0 #开启 oom fs.inotify.max_user_instances=8192 fs.inotify.max_user_watches=1048576 fs.file-max=52706963 fs.nr_open=52706963 net.ipv6.conf.all.disable_ipv6=1 #关闭不使用的ipv6协议栈,防止触发docker bug. net.netfilter.nf_conntrack_max=2310720 eof [root@k8s-master01 ~]# cp kubernetes.conf /etc/sysctl.d/kubernetes.conf [root@k8s-master01 ~]# sysctl -p /etc/sysctl.d/kubernetes.conf 这里需要注意: 必须关闭 tcp_tw_recycle,否则和 nat 冲突,会导致服务不通; 关闭 ipv6,防止触发 docker bug; 12)设置系统时区 # 调整系统 timezone [root@k8s-master01 ~]# timedatectl set-timezone asia/shanghai # 将当前的 utc 时间写入硬件时钟 [root@k8s-master01 ~]# timedatectl set-local-rtc 0 # 重启依赖于系统时间的服务 [root@k8s-master01 ~]# systemctl restart rsyslog [root@k8s-master01 ~]# systemctl restart crond 13)设置rsyslogd 和systemd journald (每台节点机都要操作) systemd 的 journald 是 centos 7 缺省的日志记录工具,它记录了所有系统、内核、service unit 的日志。相比 systemd,journald 记录的日志有如下优势: -> 可以记录到内存或文件系统;(默认记录到内存,对应的位置为 /run/log/jounal); -> 可以限制占用的磁盘空间、保证磁盘剩余空间; -> 可以限制日志文件大小、保存的时间; -> journald 默认将日志转发给 rsyslog,这会导致日志写了多份,/var/log/messages 中包含了太多无关日志,不方便后续查看,同时也影响系统性能。 [root@k8s-master01 ~]# mkdir /var/log/journal #持久化保存日志的目录 [root@k8s-master01 ~]# mkdir /etc/systemd/journald.conf.d [root@k8s-master01 ~]# cat > /etc/systemd/journald.conf.d/99-prophet.conf <<eof [journal] # 持久化保存到磁盘 storage=persistent # 压缩历史日志 compress=yes syncintervalsec=5m ratelimitinterval=30s ratelimitburst=1000 # 最大占用空间 10g systemmaxuse=10g # 单日志文件最大 200m systemmaxfilesize=200m # 日志保存时间 2 周 maxretentionsec=2week # 不将日志转发到 syslog forwardtosyslog=no eof [root@k8s-master01 ~]# systemctl restart systemd-journald 14) 创建k8s相关目录 (每台节点机都要操作) [root@k8s-master01 ~]# mkdir -p /opt/k8s/{bin,work} /etc/{kubernetes,etcd}/cert 15) 升级内核 (每台节点机都要操作) centos 7.x系统自带的3.10.x内核存在一些bugs,导致运行的docker、kubernetes不稳定,例如: -> 高版本的 docker(1.13 以后) 启用了3.10 kernel实验支持的kernel memory account功能(无法关闭),当节点压力大如频繁启动和停止容器时会导致 cgroup memory leak; -> 网络设备引用计数泄漏,会导致类似于报错:"kernel:unregister_netdevice: waiting for eth0 to become free. usage count = 1"; 解决方案如下: -> 升级内核到 4.4.x 以上; -> 或者,手动编译内核,disable config_memcg_kmem 特性; -> 或者安装修复了该问题的 docker 18.09.1 及以上的版本。但由于 kubelet 也会设置 kmem(它 vendor 了 runc),所以需要重新编译 kubelet 并指定 goflags="-tags=nokmem"; 这里升级内核方法: [root@k8s-master01 ~]# uname -r 3.10.0-862.el7.x86_64 [root@k8s-master01 ~]# rpm -uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm 安装完成后检查 /boot/grub2/grub.cfg 中对应内核 menuentry 中是否包含 initrd16 配置,如果没有,再安装一次! [root@k8s-master01 ~]# yum --enablerepo=elrepo-kernel install -y kernel-lt 设置开机从新内核启动 [root@k8s-master01 ~]# grub2-set-default 0 重启机器 [root@k8s-master01 ~]# init 6 安装内核源文件(在升级完内核并重启机器后执行,也可以不用执行这一步。可选): [root@k8s-master01 ~]# yum --enablerepo=elrepo-kernel install kernel-lt-devel-$(uname -r) kernel-lt-headers-$(uname -r) [root@k8s-master01 ~]# uname -r 4.4.180-2.el7.elrepo.x86_64 ==================================================================================================================================== 或者也可以采用下面升级内核的方法: # git clone --branch v1.14.1 --single-branch --depth 1 https://github.com/kubernetes/kubernetes # cd kubernetes # kube_git_version=v1.14.1 ./build/run.sh make kubelet goflags="-tags=nokmem" # init 6 ==================================================================================================================================== 16) 关闭numa [root@k8s-master01 ~]# cp /etc/default/grub{,.bak} [root@k8s-master01 ~]# vim /etc/default/grub ......... grub_cmdline_linux="...... numa=off" # 即添加"numa=0ff"内容 重新生成 grub2 配置文件: # cp /boot/grub2/grub.cfg{,.bak} # grub2-mkconfig -o /boot/grub2/grub.cfg 17) 变量脚本文件 (这一步很关键) [root@k8s-master01 ~]# vim /opt/k8s/bin/environment.sh #!/usr/bin/bash # 生成 encryptionconfig 所需的加密 key export encryption_key=$(head -c 32 /dev/urandom | base64) # 集群中所有节点机器ip数组(master,node,etcd节点) export node_all_ips=(172.16.60.241 172.16.60.242 172.16.60.243 172.16.60.244 172.16.60.245 172.16.60.246) # 集群中所有节点ip对应的主机名数组 export node_all_names=(k8s-master01 k8s-master02 k8s-master03 k8s-node01 k8s-node02 k8s-node03) # 集群中所有master节点集群ip数组 export node_master_ips=(172.16.60.241 172.16.60.242 172.16.60.243) # 集群中master节点ip对应的主机名数组 export node_master_names=(k8s-master01 k8s-master02 k8s-master03) # 集群中所有node节点集群ip数组 export node_node_ips=(172.16.60.244 172.16.60.245 172.16.60.246) # 集群中node节点ip对应的主机名数组 export node_node_names=(k8s-node01 k8s-node02 k8s-node03) # 集群中所有etcd节点集群ip数组 export node_etcd_ips=(172.16.60.241 172.16.60.242 172.16.60.243) # 集群中etcd节点ip对应的主机名数组(这里是和master三节点机器共用) export node_etcd_names=(k8s-etcd01 k8s-etcd02 k8s-etcd03) # etcd 集群服务地址列表 export etcd_endpoints="https://172.16.60.241:2379,https://172.16.60.242:2379,https://172.16.60.243:2379" # etcd 集群间通信的 ip 和端口 export etcd_nodes="k8s-etcd01=https://172.16.60.241:2380,k8s-etcd02=https://172.16.60.242:2380,k8s-etcd03=https://172.16.60.243:2380" # kube-apiserver 的反向代理(地址端口.这里也就是nginx代理层的vip地址 export kube_apiserver="https://172.16.60.250:8443" # 节点间互联网络接口名称. 这里我所有的centos7节点机的网卡设备是ens192,而不是eth0 export iface="ens192" # etcd 数据目录 export etcd_data_dir="/data/k8s/etcd/data" # etcd wal 目录,建议是 ssd 磁盘分区,或者和 etcd_data_dir 不同的磁盘分区 export etcd_wal_dir="/data/k8s/etcd/wal" # k8s 各组件数据目录 export k8s_dir="/data/k8s/k8s" # docker 数据目录 export docker_dir="/data/k8s/docker" ## 以下参数一般不需要修改 # tls bootstrapping 使用的 token,可以使用命令 head -c 16 /dev/urandom | od -an -t x | tr -d ' ' 生成 bootstrap_token="41f7e4ba8b7be874fcff18bf5cf41a7c" # 最好使用 当前未用的网段 来定义服务网段和 pod 网段 # 服务网段,部署前路由不可达,部署后集群内路由可达(kube-proxy 保证) service_cidr="10.254.0.0/16" # pod 网段,建议 /16 段地址,部署前路由不可达,部署后集群内路由可达(flanneld 保证) cluster_cidr="172.30.0.0/16" # 服务端口范围 (nodeport range) export node_port_range="30000-32767" # flanneld 网络配置前缀 export flannel_etcd_prefix="/kubernetes/network" # kubernetes 服务 ip (一般是 service_cidr 中第一个ip) export cluster_kubernetes_svc_ip="10.254.0.1" # 集群 dns 服务 ip (从 service_cidr 中预分配) export cluster_dns_svc_ip="10.254.0.2" # 集群 dns 域名(末尾不带点号) export cluster_dns_domain="cluster.local" # 将二进制目录 /opt/k8s/bin 加到 path 中 export path=/opt/k8s/bin:$path
三、创建集群中需要的ca证书和秘钥
为确保安全,kubernetes 系统各组件需要使用 x509 证书对通信进行加密和认证。ca (certificate authority) 是自签名的根证书,用来签名后续创建的其它证书。这里使用 cloudflare 的 pki 工具集 cfssl 创建所有证书。下面部署命令均在k8s-master01节点上执行,然后远程分发文件和执行命令。
1)安装cfssl工具集 [root@k8s-master01 ~]# mkdir -p /opt/k8s/work && cd /opt/k8s/work [root@k8s-master01 work]# wget https://pkg.cfssl.org/r1.2/cfssl_linux-amd64 [root@k8s-master01 work]# mv cfssl_linux-amd64 /opt/k8s/bin/cfssl [root@k8s-master01 work]# wget https://pkg.cfssl.org/r1.2/cfssljson_linux-amd64 [root@k8s-master01 work]# mv cfssljson_linux-amd64 /opt/k8s/bin/cfssljson [root@k8s-master01 work]# wget https://pkg.cfssl.org/r1.2/cfssl-certinfo_linux-amd64 [root@k8s-master01 work]# mv cfssl-certinfo_linux-amd64 /opt/k8s/bin/cfssl-certinfo [root@k8s-master01 work]# chmod +x /opt/k8s/bin/* [root@k8s-master01 work]# export path=/opt/k8s/bin:$path 2)创建根证书 (ca) ca 证书是集群所有节点共享的,只需要创建一个 ca 证书,后续创建的所有证书都由它签名。 2.1)创建配置文件 ca 配置文件用于配置根证书的使用场景 (profile) 和具体参数 (usage,过期时间、服务端认证、客户端认证、加密等),后续在签名其它证书时需要指定特定场景。 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# cat > ca-config.json <<eof { "signing": { "default": { "expiry": "87600h" }, "profiles": { "kubernetes": { "usages": [ "signing", "key encipherment", "server auth", "client auth" ], "expiry": "87600h" } } } } eof 配置说明: signing:表示该证书可用于签名其它证书,生成的 ca.pem 证书中 ca=true; server auth:表示 client 可以用该该证书对 server 提供的证书进行验证; client auth:表示 server 可以用该该证书对 client 提供的证书进行验证; 2.2)创建证书签名请求文件 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# cat > ca-csr.json <<eof { "cn": "kubernetes", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "c": "cn", "st": "beijing", "l": "beijing", "o": "k8s", "ou": "4paradigm" } ] } eof 配置说明: cn:common name,kube-apiserver 从证书中提取该字段作为请求的用户名 (user name),浏览器使用该字段验证网站是否合法; o:organization,kube-apiserver 从证书中提取该字段作为请求用户所属的组 (group); kube-apiserver 将提取的 user、group 作为 rbac 授权的用户标识; 2.3)生成 ca 证书和私钥 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# cfssl gencert -initca ca-csr.json | cfssljson -bare ca [root@k8s-master01 work]# ls ca* ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem [root@k8s-master01 work]# 3)分发证书文件 将生成的 ca 证书、秘钥文件、配置文件拷贝到所有节点的 /etc/kubernetes/cert 目录下: [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_all_ip in ${node_all_ips[@]} do echo ">>> ${node_all_ip}" ssh root@${node_all_ip} "mkdir -p /etc/kubernetes/cert" scp ca*.pem ca-config.json root@${node_all_ip}:/etc/kubernetes/cert done
四、部署kubectl命令行工具
kubectl 是 kubernetes 集群的命令行管理工具. kubectl 默认从 ~/.kube/config 文件读取kube-apiserver地址和认证信息,如果没有配置,执行kubectl命令时就会报错!kubectl只需要部署一次,生成的kubeconfig文件是通用的,可以拷贝到需要执行kubectl命令的节点机器,重命名为 ~/.kube/config;这里我将kubectl节点只部署到三个master节点机器上,其他节点不部署kubectl命令。也就是说后续进行kubectl命令管理就只能在master节点上操作。下面部署命令均在k8s-master01节点上执行,然后远程分发文件和执行命令。
如果没有部署kubectl工具,则执行时会报错说没有该命令: [root@k8s-master01 ~]# kubectl get pods -bash: kubectl: command not found 1)下载和分发kubectl二进制文件 二进制包下载地址:https://pan.baidu.com/s/1huwfqkvlyxizox2ldqsebg 提取密码:7kaf [root@k8s-master01 ~]# cd /opt/k8s/work [root@k8s-master01 work]# wget https://dl.k8s.io/v1.14.2/kubernetes-client-linux-amd64.tar.gz [root@k8s-master01 work]# tar -xzvf kubernetes-client-linux-amd64.tar.gz 分发到所有使用kubectl的节点,这里只分发到三个master节点 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_master_ip in ${node_master_ips[@]} do echo ">>> ${node_master_ip}" scp kubernetes/client/bin/kubectl root@${node_master_ip}:/opt/k8s/bin/ ssh root@${node_master_ip} "chmod +x /opt/k8s/bin/*" done 2) 创建admin证书和私钥 kubectl与apiserver https安全端口通信,apiserver 对提供的证书进行认证和授权。 kubectl作为集群的管理工具,需要被授予最高权限,这里创建具有最高权限的 admin 证书。 创建证书签名请求: [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# cat > admin-csr.json <<eof { "cn": "admin", "hosts": [], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "c": "cn", "st": "beijing", "l": "beijing", "o": "system:masters", "ou": "4paradigm" } ] } eof 配置说明: o为system:masters,kube-apiserver 收到该证书后将请求的 group 设置为 system:masters; 预定义的 clusterrolebinding cluster-admin 将group system:masters 与 role cluster-admin 绑定,该 role 授予所有 api的权限; 该证书只会被kubectl当做client证书使用,所以hosts字段为空; 生成证书和私钥: [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# cfssl gencert -ca=/opt/k8s/work/ca.pem \ -ca-key=/opt/k8s/work/ca-key.pem \ -config=/opt/k8s/work/ca-config.json \ -profile=kubernetes admin-csr.json | cfssljson -bare admin [root@k8s-master01 work]# ls admin* admin.csr admin-csr.json admin-key.pem admin.pem 3)创建 kubeconfig 文件 kubeconfig 为 kubectl 的配置文件,包含访问 apiserver 的所有信息,如 apiserver 地址、ca 证书和自身使用的证书; [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh 设置集群参数 [root@k8s-master01 work]# kubectl config set-cluster kubernetes \ --certificate-authority=/opt/k8s/work/ca.pem \ --embed-certs=true \ --server=${kube_apiserver} \ --kubeconfig=kubectl.kubeconfig 设置客户端认证参数 [root@k8s-master01 work]# kubectl config set-credentials admin \ --client-certificate=/opt/k8s/work/admin.pem \ --client-key=/opt/k8s/work/admin-key.pem \ --embed-certs=true \ --kubeconfig=kubectl.kubeconfig 设置上下文参数 [root@k8s-master01 work]# kubectl config set-context kubernetes \ --cluster=kubernetes \ --user=admin \ --kubeconfig=kubectl.kubeconfig 设置默认上下文 [root@k8s-master01 work]# kubectl config use-context kubernetes --kubeconfig=kubectl.kubeconfig 配置说明: --certificate-authority:验证 kube-apiserver 证书的根证书; --client-certificate、--client-key:刚生成的 admin 证书和私钥,连接 kube-apiserver 时使用; --embed-certs=true:将 ca.pem 和 admin.pem 证书内容嵌入到生成的 kubectl.kubeconfig 文件中(不加时,写入的是证书文件路径, 后续拷贝 kubeconfig 到其它机器时,还需要单独拷贝证书文件,这就很不方便了) 4)分发 kubeconfig 文件, 保存的文件名为 ~/.kube/config; 分发到所有使用 kubectl 命令的节点,即分发到三个master节点上 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_master_ip in ${node_master_ips[@]} do echo ">>> ${node_master_ip}" ssh root@${node_master_ip} "mkdir -p ~/.kube" scp kubectl.kubeconfig root@${node_master_ip}:~/.kube/config done
五、部署etcd集群
etcd是基于raft的分布式key-value存储系统,由coreos开发,常用于服务发现、共享配置以及并发控制(如leader选举、分布式锁等)。kubernetes使用etcd存储所有运行数据。需要注意的是:由于etcd是负责存储,所以不建议搭建单点集群,如zookeeper一样,由于存在选举策略,所以一般推荐奇数个集群,如3,5,7。只要集群半数以上的结点存活,那么集群就可以正常运行,否则集群可能无法正常使用。下面部署命令均在k8s-master01节点上执行,然后远程分发文件和执行命令。
1)下载和分发etcd二进制文件 [root@k8s-master01 ~]# cd /opt/k8s/work [root@k8s-master01 work]# wget https://github.com/coreos/etcd/releases/download/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz [root@k8s-master01 work]# tar -xvf etcd-v3.3.13-linux-amd64.tar.gz 分发二进制文件到etcd集群所有节点: [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_etcd_ip in ${node_etcd_ips[@]} do echo ">>> ${node_etcd_ip}" scp etcd-v3.3.13-linux-amd64/etcd* root@${node_etcd_ip}:/opt/k8s/bin ssh root@${node_etcd_ip} "chmod +x /opt/k8s/bin/*" done 2) 创建etcd证书和私钥 创建证书签名请求: [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# cat > etcd-csr.json <<eof { "cn": "etcd", "hosts": [ "127.0.0.1", "172.16.60.241", "172.16.60.242", "172.16.60.243" ], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "c": "cn", "st": "beijing", "l": "beijing", "o": "k8s", "ou": "4paradigm" } ] } eof 配置说明: hosts 字段指定授权使用该证书的 etcd 节点 ip 或域名列表,需要将 etcd 集群的三个节点 ip 都列在其中; 生成证书和私钥 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# cfssl gencert -ca=/opt/k8s/work/ca.pem \ -ca-key=/opt/k8s/work/ca-key.pem \ -config=/opt/k8s/work/ca-config.json \ -profile=kubernetes etcd-csr.json | cfssljson -bare etcd [root@k8s-master01 work]# ls etcd*pem etcd-key.pem etcd.pem 分发生成的证书和私钥到各etcd节点 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_etcd_ip in ${node_etcd_ips[@]} do echo ">>> ${node_etcd_ip}" ssh root@${node_etcd_ip} "mkdir -p /etc/etcd/cert" scp etcd*.pem root@${node_etcd_ip}:/etc/etcd/cert/ done 3) 创建etcd的systemd unit模板文件 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# cat > etcd.service.template <<eof [unit] description=etcd server after=network.target after=network-online.target wants=network-online.target documentation=https://github.com/coreos [service] type=notify workingdirectory=${etcd_data_dir} execstart=/opt/k8s/bin/etcd \\ --data-dir=${etcd_data_dir} \\ --wal-dir=${etcd_wal_dir} \\ --name=##node_etcd_name## \\ --cert-file=/etc/etcd/cert/etcd.pem \\ --key-file=/etc/etcd/cert/etcd-key.pem \\ --trusted-ca-file=/etc/kubernetes/cert/ca.pem \\ --peer-cert-file=/etc/etcd/cert/etcd.pem \\ --peer-key-file=/etc/etcd/cert/etcd-key.pem \\ --peer-trusted-ca-file=/etc/kubernetes/cert/ca.pem \\ --peer-client-cert-auth \\ --client-cert-auth \\ --listen-peer-urls=https://##node_etcd_ip##:2380 \\ --initial-advertise-peer-urls=https://##node_etcd_ip##:2380 \\ --listen-client-urls=https://##node_etcd_ip##:2379,http://127.0.0.1:2379 \\ --advertise-client-urls=https://##node_etcd_ip##:2379 \\ --initial-cluster-token=etcd-cluster-0 \\ --initial-cluster=${etcd_nodes} \\ --initial-cluster-state=new \\ --auto-compaction-mode=periodic \\ --auto-compaction-retention=1 \\ --max-request-bytes=33554432 \\ --quota-backend-bytes=6442450944 \\ --heartbeat-interval=250 \\ --election-timeout=2000 restart=on-failure restartsec=5 limitnofile=65536 [install] wantedby=multi-user.target eof 配置说明: workingdirectory、--data-dir:指定工作目录和数据目录为 ${etcd_data_dir},需在启动服务前创建这个目录; --wal-dir:指定 wal 目录,为了提高性能,一般使用 ssd 或者和 --data-dir 不同的磁盘; --name:指定节点名称,当 --initial-cluster-state 值为 new 时,--name 的参数值必须位于 --initial-cluster 列表中; --cert-file、--key-file:etcd server 与 client 通信时使用的证书和私钥; --trusted-ca-file:签名 client 证书的 ca 证书,用于验证 client 证书; --peer-cert-file、--peer-key-file:etcd 与 peer 通信使用的证书和私钥; --peer-trusted-ca-file:签名 peer 证书的 ca 证书,用于验证 peer 证书; 4)为各etcd节点创建和分发 etcd systemd unit 文件 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for (( i=0; i < 3; i++ )) do sed -e "s/##node_etcd_name##/${node_etcd_names[i]}/" -e "s/##node_etcd_ip##/${node_etcd_ips[i]}/" etcd.service.template > etcd-${node_etcd_ips[i]}.service done [root@k8s-master01 work]# ls *.service etcd-172.16.60.241.service etcd-172.16.60.242.service etcd-172.16.60.243.service 最好手动查看其中一个etcd节点的启动文件里的--name名称和ip是否都已修改过来了 [root@k8s-master01 work]# cat etcd-172.16.60.241.service ....... --name=k8s-etcd01 \ ....... --listen-peer-urls=https://172.16.60.241:2380 \ --initial-advertise-peer-urls=https://172.16.60.241:2380 \ --listen-client-urls=https://172.16.60.241:2379,http://127.0.0.1:2379 \ --advertise-client-urls=https://172.16.60.241:2379 \ --initial-cluster-token=etcd-cluster-0 \ --initial-cluster=k8s-etcd01=https://172.16.60.241:2380,k8s-etcd02=https://172.16.60.242:2380,k8s-etcd03=https://172.16.60.243:2380 \ ....... 配置说明: node_etcd_names 和 node_etcd_ips 为相同长度的bash数组,分别为etcd集群节点名称和对应的ip; 分发生成的 systemd unit 文件: [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_etcd_ip in ${node_etcd_ips[@]} do echo ">>> ${node_etcd_ip}" scp etcd-${node_etcd_ip}.service root@${node_etcd_ip}:/etc/systemd/system/etcd.service done 配置说明: 文件重命名为 etcd.service; 5)启动 etcd 服务 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_etcd_ip in ${node_etcd_ips[@]} do echo ">>> ${node_etcd_ip}" ssh root@${node_etcd_ip} "mkdir -p ${etcd_data_dir} ${etcd_wal_dir}" ssh root@${node_etcd_ip} "systemctl daemon-reload && systemctl enable etcd && systemctl restart etcd " & done 配置说明: 必须先创建 etcd 数据目录和工作目录; etcd 进程首次启动时会等待其它节点的 etcd 加入集群,命令 systemctl start etcd 会卡住一段时间,为正常现象; 6)检查etcd服务启动结果 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_etcd_ip in ${node_etcd_ips[@]} do echo ">>> ${node_etcd_ip}" ssh root@${node_etcd_ip} "systemctl status etcd|grep active" done 预期输出结果为: >>> 172.16.60.241 active: active (running) since tue 2019-06-04 19:55:32 cst; 7min ago >>> 172.16.60.242 active: active (running) since tue 2019-06-04 19:55:32 cst; 7min ago >>> 172.16.60.243 active: active (running) since tue 2019-06-04 19:55:32 cst; 7min ago 确保状态均为为active (running),否则查看日志,确认原因 (可以执行"journalctl -u etcd"命令查看启动失败原因) 6)验证服务状态 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_etcd_ip in ${node_etcd_ips[@]} do echo ">>> ${node_etcd_ip}" ssh root@${node_etcd_ip} " etcdctl_api=3 /opt/k8s/bin/etcdctl \ --endpoints=https://${node_etcd_ip}:2379 \ --cacert=/etc/kubernetes/cert/ca.pem \ --cert=/etc/etcd/cert/etcd.pem \ --key=/etc/etcd/cert/etcd-key.pem endpoint health " done 预期输出结果为: https://172.16.60.241:2379 is healthy: successfully committed proposal: took = 2.44394ms >>> 172.16.60.242 https://172.16.60.242:2379 is healthy: successfully committed proposal: took = 7.044349ms >>> 172.16.60.243 https://172.16.60.243:2379 is healthy: successfully committed proposal: took = 1.865713ms 输出均为 healthy 时表示集群服务正常。 7)查看当前etcd集群中的leader 在三台etcd节点中的任意一个节点机器上执行下面命令: [root@k8s-etcd03 ~]# source /opt/k8s/bin/environment.sh [root@k8s-etcd03 ~]# etcdctl_api=3 /opt/k8s/bin/etcdctl \ -w table --cacert=/etc/kubernetes/cert/ca.pem \ --cert=/etc/etcd/cert/etcd.pem \ --key=/etc/etcd/cert/etcd-key.pem \ --endpoints=${etcd_endpoints} endpoint status 预期输出结果为: +----------------------------+------------------+---------+---------+-----------+-----------+------------+ | endpoint | id | version | db size | is leader | raft term | raft index | +----------------------------+------------------+---------+---------+-----------+-----------+------------+ | https://172.16.60.241:2379 | 577381f5de0f4495 | 3.3.13 | 16 kb | false | 2 | 8 | | https://172.16.60.242:2379 | bf4ce221cdf39fb0 | 3.3.13 | 16 kb | false | 2 | 8 | | https://172.16.60.243:2379 | 3bc2e49bc639590 | 3.3.13 | 16 kb | true | 2 | 8 | +----------------------------+------------------+---------+---------+-----------+-----------+------------+ 由上面结果可见,当前的leader节点为172.16.60.243
六、flannel容器网络方案部署
kubernetes要求集群内各节点(这里指master和node节点)能通过pod网段互联互通。flannel使用vxlan技术为各节点创建一个可以互通的pod网络,使用的端口为udp 8472(需要开放该端口,如公有云aws等)。flanneld第一次启动时,从etcd获取配置的pod网段信息,为本节点分配一个未使用的地址段,然后创建flannedl.1网络接口(也可能是其它名称,如flannel1等)。flannel将分配给自己的pod网段信息写入/run/flannel/docker文件,docker后续使用这个文件中的环境变量设置docker0网桥,从而从这个地址段为本节点的所有pod容器分配ip。下面部署命令均在k8s-master01节点上执行,然后远程分发文件和执行命令。
1) 下载和分发 flanneld 二进制文件 从flannel的release页面(https://github.com/coreos/flannel/releases)下载最新版本的安装包: [root@k8s-master01 ~]# cd /opt/k8s/work [root@k8s-master01 work]# mkdir flannel [root@k8s-master01 work]# wget https://github.com/coreos/flannel/releases/download/v0.11.0/flannel-v0.11.0-linux-amd64.tar.gz [root@k8s-master01 work]# tar -zvxf flannel-v0.11.0-linux-amd64.tar.gz -c flannel 分发二进制文件到集群所有节点: [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_all_ip in ${node_all_ips[@]} do echo ">>> ${node_all_ip}" scp flannel/{flanneld,mk-docker-opts.sh} root@${node_all_ip}:/opt/k8s/bin/ ssh root@${node_all_ip} "chmod +x /opt/k8s/bin/*" done 2) 创建 flannel 证书和私钥 flanneld 从 etcd 集群存取网段分配信息,而 etcd 集群启用了双向 x509 证书认证,所以需要为 flanneld 生成证书和私钥。 创建证书签名请求: [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# cat > flanneld-csr.json <<eof { "cn": "flanneld", "hosts": [], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "c": "cn", "st": "beijing", "l": "beijing", "o": "k8s", "ou": "4paradigm" } ] } eof 该证书只会被 kubectl 当做 client 证书使用,所以 hosts 字段为空; 生成证书和私钥: [root@k8s-master01 work]# cfssl gencert -ca=/opt/k8s/work/ca.pem \ -ca-key=/opt/k8s/work/ca-key.pem \ -config=/opt/k8s/work/ca-config.json \ -profile=kubernetes flanneld-csr.json | cfssljson -bare flanneld 将生成的证书和私钥分发到所有节点(master 和 node): [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_all_ip in ${node_all_ips[@]} do echo ">>> ${node_all_ip}" ssh root@${node_all_ip} "mkdir -p /etc/flanneld/cert" scp flanneld*.pem root@${node_all_ip}:/etc/flanneld/cert done 3)向 etcd 写入集群 pod 网段信息 (注意:本步骤只需执行一次) [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# etcdctl \ --endpoints=${etcd_endpoints} \ --ca-file=/opt/k8s/work/ca.pem \ --cert-file=/opt/k8s/work/flanneld.pem \ --key-file=/opt/k8s/work/flanneld-key.pem \ mk ${flannel_etcd_prefix}/config '{"network":"'${cluster_cidr}'", "subnetlen": 21, "backend": {"type": "vxlan"}}' 解决说明: flanneld 当前版本 (v0.11.0) 不支持 etcd v3,故使用 etcd v2 api 写入配置 key 和网段数据; 写入的 pod 网段 ${cluster_cidr} 地址段(如 /16)必须小于 subnetlen,必须与 kube-controller-manager 的 --cluster-cidr 参数值一致; 4)创建 flanneld 的 systemd unit 文件 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# cat > flanneld.service << eof [unit] description=flanneld overlay address etcd agent after=network.target after=network-online.target wants=network-online.target after=etcd.service before=docker.service [service] type=notify execstart=/opt/k8s/bin/flanneld \\ -etcd-cafile=/etc/kubernetes/cert/ca.pem \\ -etcd-certfile=/etc/flanneld/cert/flanneld.pem \\ -etcd-keyfile=/etc/flanneld/cert/flanneld-key.pem \\ -etcd-endpoints=${etcd_endpoints} \\ -etcd-prefix=${flannel_etcd_prefix} \\ -iface=${iface} \\ -ip-masq execstartpost=/opt/k8s/bin/mk-docker-opts.sh -k docker_network_options -d /run/flannel/docker restart=always restartsec=5 startlimitinterval=0 [install] wantedby=multi-user.target requiredby=docker.service eof 解决说明: mk-docker-opts.sh 脚本将分配给 flanneld 的 pod 子网段信息写入 /run/flannel/docker 文件,后续 docker 启动时使用这个文件中的环境变量配置 docker0 网桥; flanneld 使用系统缺省路由所在的接口与其它节点通信,对于有多个网络接口(如内网和公网)的节点,可以用 -iface 参数指定通信接口; flanneld 运行时需要 root 权限; -ip-masq: flanneld 为访问 pod 网络外的流量设置 snat 规则,同时将传递给 docker 的变量 --ip-masq(/run/flannel/docker 文件中)设置为 false,这样 docker 将不再创建 snat 规则; docker 的 --ip-masq 为 true 时,创建的 snat 规则比较“暴力”:将所有本节点 pod 发起的、访问非 docker0 接口的请求做 snat,这样访问其他节点 pod 的请求来源 ip 会被设置为 flannel.1 接口的 ip,导致目的 pod 看不到真实的来源 pod ip。 flanneld 创建的 snat 规则比较温和,只对访问非 pod 网段的请求做 snat。 5)分发 flanneld systemd unit 文件到所有节点 [root@k8s-master01 work]# cd /opt/k8s/work [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_all_ip in ${node_all_ips[@]} do echo ">>> ${node_all_ip}" scp flanneld.service root@${node_all_ip}:/etc/systemd/system/ done 6)启动 flanneld 服务 [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_all_ip in ${node_all_ips[@]} do echo ">>> ${node_all_ip}" ssh root@${node_all_ip} "systemctl daemon-reload && systemctl enable flanneld && systemctl restart flanneld" done 6)检查启动结果 [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_all_ip in ${node_all_ips[@]} do echo ">>> ${node_all_ip}" ssh root@${node_all_ip} "systemctl status flanneld|grep active" done 确保状态为 active (running),否则查看日志,确认原因"journalctl -u flanneld" 7) 检查分配给各 flanneld 的 pod 网段信息 查看集群 pod 网段(/16): [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# etcdctl \ --endpoints=${etcd_endpoints} \ --ca-file=/etc/kubernetes/cert/ca.pem \ --cert-file=/etc/flanneld/cert/flanneld.pem \ --key-file=/etc/flanneld/cert/flanneld-key.pem \ get ${flannel_etcd_prefix}/config 预期输出: {"network":"172.30.0.0/16", "subnetlen": 21, "backend": {"type": "vxlan"}} 查看已分配的 pod 子网段列表(/24): [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# etcdctl \ --endpoints=${etcd_endpoints} \ --ca-file=/etc/kubernetes/cert/ca.pem \ --cert-file=/etc/flanneld/cert/flanneld.pem \ --key-file=/etc/flanneld/cert/flanneld-key.pem \ ls ${flannel_etcd_prefix}/subnets 预期输出: /kubernetes/network/subnets/172.30.40.0-21 /kubernetes/network/subnets/172.30.88.0-21 /kubernetes/network/subnets/172.30.56.0-21 /kubernetes/network/subnets/172.30.72.0-21 /kubernetes/network/subnets/172.30.232.0-21 /kubernetes/network/subnets/172.30.152.0-21 查看某一 pod 网段对应的节点 ip 和 flannel 接口地址: [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# etcdctl \ --endpoints=${etcd_endpoints} \ --ca-file=/etc/kubernetes/cert/ca.pem \ --cert-file=/etc/flanneld/cert/flanneld.pem \ --key-file=/etc/flanneld/cert/flanneld-key.pem \ get ${flannel_etcd_prefix}/subnets/172.30.40.0-21 预期输出:{"publicip":"172.16.60.243","backendtype":"vxlan","backenddata":{"vtepmac":"f2:de:47:06:4b:d3"}} 解决说明: 172.30.40.0/21 被分配给节点k8s-master03(172.16.60.243); vtepmac 为k8s-master03节点的 flannel.1 网卡 mac 地址; 8)检查节点 flannel 网络信息 (比如k8s-master01节点) [root@k8s-master01 work]# ip addr show 1: lo: <loopback,up,lower_up> mtu 65536 qdisc noqueue state unknown group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 2: ens192: <broadcast,multicast,up,lower_up> mtu 1500 qdisc mq state up group default qlen 1000 link/ether 00:50:56:ac:7c:81 brd ff:ff:ff:ff:ff:ff inet 172.16.60.241/24 brd 172.16.60.255 scope global ens192 valid_lft forever preferred_lft forever 3: flannel.1: <broadcast,multicast,up,lower_up> mtu 1450 qdisc noqueue state unknown group default link/ether 7a:2a:36:99:75:5f brd ff:ff:ff:ff:ff:ff inet 172.30.232.0/32 scope global flannel.1 valid_lft forever preferred_lft forever 注意: flannel.1 网卡的地址为分配的 pod 子网段的第一个 ip(.0),且是 /32 的地址; [root@k8s-master01 work]# ip route show |grep flannel.1 172.30.40.0/21 via 172.30.40.0 dev flannel.1 onlink 172.30.56.0/21 via 172.30.56.0 dev flannel.1 onlink 172.30.72.0/21 via 172.30.72.0 dev flannel.1 onlink 172.30.88.0/21 via 172.30.88.0 dev flannel.1 onlink 172.30.152.0/21 via 172.30.152.0 dev flannel.1 onlink 到其它节点 pod 网段请求都被转发到 flannel.1 网卡; flanneld 根据 etcd 中子网段的信息,如 ${flannel_etcd_prefix}/subnets/172.30.232.0-21 ,来决定进请求发送给哪个节点的互联 ip; 9)验证各节点能通过 pod 网段互通 在各节点上部署 flannel 后,检查是否创建了 flannel 接口(名称可能为 flannel0、flannel.0、flannel.1 等): [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_all_ip in ${node_all_ips[@]} do echo ">>> ${node_all_ip}" ssh ${node_all_ip} "/usr/sbin/ip addr show flannel.1|grep -w inet" done 预期输出: >>> 172.16.60.241 inet 172.30.232.0/32 scope global flannel.1 >>> 172.16.60.242 inet 172.30.152.0/32 scope global flannel.1 >>> 172.16.60.243 inet 172.30.40.0/32 scope global flannel.1 >>> 172.16.60.244 inet 172.30.88.0/32 scope global flannel.1 >>> 172.16.60.245 inet 172.30.56.0/32 scope global flannel.1 >>> 172.16.60.246 inet 172.30.72.0/32 scope global flannel.1 在各节点上 ping 所有 flannel 接口 ip,确保能通: [root@k8s-master01 work]# source /opt/k8s/bin/environment.sh [root@k8s-master01 work]# for node_all_ip in ${node_all_ips[@]} do echo ">>> ${node_all_ip}" ssh ${node_all_ip} "ping -c 1 172.30.232.0" ssh ${node_all_ip} "ping -c 1 172.30.152.0" ssh ${node_all_ip} "ping -c 1 172.30.40.0" ssh ${node_all_ip} "ping -c 1 172.30.88.0" ssh ${node_all_ip} "ping -c 1 172.30.56.0" ssh ${node_all_ip} "ping -c 1 172.30.72.0" done
七、基于nginx 四层代理环境
这里采用nginx 4 层透明代理功能实现 k8s 节点( master 节点和 worker 节点)高可用访问 kube-apiserver。控制节点的 kube-controller-manager、kube-scheduler 是多实例(3个)部署,所以只要有一个实例正常,就可以保证高可用;搭建nginx+keepalived环境,对外提供一个统一的vip地址,后端对接多个 apiserver 实例,nginx 对它们做健康检查和负载均衡;kubelet、kube-proxy、controller-manager、scheduler 通过vip地址访问 kube-apiserver,从而实现 kube-apiserver 的高可用;
一、安装和配置nginx,下面操作在172.16.60.247、172.16.60.247两个节点机器上操作 1)下载和编译 nginx [root@k8s-ha01 ~]# yum -y install gcc pcre-devel zlib-devel openssl-devel wget lsof [root@k8s-ha01 ~]# cd /opt/k8s/work [root@k8s-ha01 work]# wget http://nginx.org/download/nginx-1.15.3.tar.gz [root@k8s-ha01 work]# tar -xzvf nginx-1.15.3.tar.gz [root@k8s-ha01 work]# cd nginx-1.15.3 [root@k8s-ha01 nginx-1.15.3]# mkdir nginx-prefix [root@k8s-ha01 nginx-1.15.3]# ./configure --with-stream --without-http --prefix=$(pwd)/nginx-prefix --without-http_uwsgi_module --without-http_scgi_module --without-http_fastcgi_module 解决说明: --with-stream:开启 4 层透明转发(tcp proxy)功能; --without-xxx:关闭所有其他功能,这样生成的动态链接二进制程序依赖最小; 预期输出: configuration summary + pcre library is not used + openssl library is not used + zlib library is not used nginx path prefix: "/root/tmp/nginx-1.15.3/nginx-prefix" nginx binary file: "/root/tmp/nginx-1.15.3/nginx-prefix/sbin/nginx" nginx modules path: "/root/tmp/nginx-1.15.3/nginx-prefix/modules" nginx configuration prefix: "/root/tmp/nginx-1.15.3/nginx-prefix/conf" nginx configuration file: "/root/tmp/nginx-1.15.3/nginx-prefix/conf/nginx.conf" nginx pid file: "/root/tmp/nginx-1.15.3/nginx-prefix/logs/nginx.pid" nginx error log file: "/root/tmp/nginx-1.15.3/nginx-prefix/logs/error.log" nginx http access log file: "/root/tmp/nginx-1.15.3/nginx-prefix/logs/access.log" nginx http client request body temporary files: "client_body_temp" nginx http proxy temporary files: "proxy_temp" 继续编译和安装: [root@k8s-ha01 nginx-1.15.3]# make && make install 2)验证编译的 nginx [root@k8s-ha01 nginx-1.15.3]# ./nginx-prefix/sbin/nginx -v nginx version: nginx/1.15.3 查看 nginx 动态链接的库: [root@k8s-ha01 nginx-1.15.3]# ldd ./nginx-prefix/sbin/nginx linux-vdso.so.1 => (0x00007ffc7e0ef000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f00b5c2d000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f00b5a11000) libc.so.6 => /lib64/libc.so.6 (0x00007f00b5644000) /lib64/ld-linux-x86-64.so.2 (0x00007f00b5e31000) 由于只开启了 4 层透明转发功能,所以除了依赖 libc 等操作系统核心 lib 库外,没有对其它 lib 的依赖(如 libz、libssl 等),这样可以方便部署到各版本操作系统中; 3)安装和部署 nginx [root@k8s-ha01 ~]# cp /opt/k8s/work/nginx-1.15.3/nginx-prefix/sbin/nginx /opt/k8s/kube-nginx/sbin/kube-nginx [root@k8s-ha01 ~]# chmod a+x /opt/k8s/kube-nginx/sbin/* [root@k8s-ha01 ~]# mkdir -p /opt/k8s/kube-nginx/{conf,logs,sbin} 配置 nginx,开启 4 层透明转发功能: [root@k8s-ha01 ~]# vim /opt/k8s/kube-nginx/conf/kube-nginx.conf worker_processes 2; events { worker_connections 65525; } stream { upstream backend { hash $remote_addr consistent; server 172.16.60.241:6443 max_fails=3 fail_timeout=30s; server 172.16.60.242:6443 max_fails=3 fail_timeout=30s; server 172.16.60.243:6443 max_fails=3 fail_timeout=30s; } server { listen 8443; proxy_connect_timeout 1s; proxy_pass backend; } } [root@k8s-ha01 ~]# ulimit -n 65525 [root@k8s-ha01 ~]# vim /etc/security/limits.conf # 文件底部添加下面四行内容 * soft nofile 65525 * hard nofile 65525 * soft nproc 65525 * hard nproc 65525 4) 配置 systemd unit 文件,启动服务 [root@k8s-ha01 ~]# vim /etc/systemd/system/kube-nginx.service [unit] description=kube-apiserver nginx proxy after=network.target after=network-online.target wants=network-online.target [service] type=forking execstartpre=/opt/k8s/kube-nginx/sbin/kube-nginx -c /opt/k8s/kube-nginx/conf/kube-nginx.conf -p /opt/k8s/kube-nginx -t execstart=/opt/k8s/kube-nginx/sbin/kube-nginx -c /opt/k8s/kube-nginx/conf/kube-nginx.conf -p /opt/k8s/kube-nginx execreload=/opt/k8s/kube-nginx/sbin/kube-nginx -c /opt/k8s/kube-nginx/conf/kube-nginx.conf -p /opt/k8s/kube-nginx -s reload privatetmp=true restart=always restartsec=5 startlimitinterval=0 limitnofile=65536 [install] wantedby=multi-user.target [root@k8s-ha01 ~]# systemctl daemon-reload && systemctl enable kube-nginx && systemctl restart kube-nginx [root@k8s-ha01 ~]# lsof -i:8443 command pid user fd type device size/off node name kube-ngin 31980 root 5u ipv4 145789 0t0 tcp localhost:pcsync-https (listen) kube-ngin 31981 nobody 5u ipv4 145789 0t0 tcp localhost:pcsync-https (listen) kube-ngin 31982 nobody 5u ipv4 145789 0t0 tcp localhost:pcsync-https (listen) 测试下8443代理端口连通性 [root@k8s-ha01 ~]# telnet 172.16.60.250 8443 trying 172.16.60.250... connected to 172.16.60.250. escape character is '^]'. connection closed by foreign host. 这是因为三个kube-apiserver服务还没有部署,即后端三个apiserver实例的6443端口还没有起来。 二、安装和配置keepalived 1)编译安装keepalived (两个节点上同样操作) [root@k8s-ha01 ~]# cd /opt/k8s/work/ [root@k8s-ha01 work]# wget https://www.keepalived.org/software/keepalived-2.0.16.tar.gz [root@k8s-ha01 work]# tar -zvxf keepalived-2.0.16.tar.gz [root@k8s-ha01 work]# cd keepalived-2.0.16 [root@k8s-ha01 keepalived-2.0.16]# ./configure [root@k8s-ha01 keepalived-2.0.16]# make && make install [root@k8s-ha01 keepalived-2.0.16]# cp keepalived/etc/init.d/keepalived /etc/rc.d/init.d/ [root@k8s-ha01 keepalived-2.0.16]# cp /usr/local/etc/sysconfig/keepalived /etc/sysconfig/ [root@k8s-ha01 keepalived-2.0.16]# mkdir /etc/keepalived [root@k8s-ha01 keepalived-2.0.16]# cp /usr/local/etc/keepalived/keepalived.conf /etc/keepalived/ [root@k8s-ha01 keepalived-2.0.16]# cp /usr/local/sbin/keepalived /usr/sbin/ [root@k8s-ha01 keepalived-2.0.16]# echo "/etc/init.d/keepalived start" >> /etc/rc.local 2) 配置keepalived 172.16.60.207节点上的keepalived配置内容 [root@k8s-ha01 ~]# cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak [root@k8s-ha01 ~]# >/etc/keepalived/keepalived.conf [root@k8s-ha01 ~]# vim /etc/keepalived/keepalived.conf ! configuration file for keepalived global_defs { notification_email { ops@wangshibo.cn tech@wangshibo.cn } notification_email_from ops@wangshibo.cn smtp_server 127.0.0.1 smtp_connect_timeout 30 router_id master-node } vrrp_script chk_http_port { script "/opt/chk_nginx.sh" interval 2 weight -5 fall 2 rise 1 } vrrp_instance vi_1 { state master interface ens192 mcast_src_ip 172.16.60.247 virtual_router_id 51 priority 101 advert_int 1 authentication { auth_type pass auth_pass 1111 } virtual_ipaddress { 172.16.60.250 } track_script { chk_http_port } } 另一个节点172.16.60.248上的keepalived配置内容为: [root@k8s-ha02 ~]# cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak [root@k8s-ha02 ~]# >/etc/keepalived/keepalived.conf [root@k8s-ha02 ~]# vim /etc/keepalived/keepalived.conf ! configuration file for keepalived global_defs { notification_email { ops@wangshibo.cn tech@wangshibo.cn } notification_email_from ops@wangshibo.cn smtp_server 127.0.0.1 smtp_connect_timeout 30 router_id slave-node } vrrp_script chk_http_port { script "/opt/chk_nginx.sh" interval 2 weight -5 fall 2 rise 1 } vrrp_instance vi_1 { state master interface ens192 mcast_src_ip 172.16.60.248 virtual_router_id 51 priority 99 advert_int 1 authentication { auth_type pass auth_pass 1111 } virtual_ipaddress { 172.16.60.250 } track_script { chk_http_port } } 2) 配置两个节点的nginx监控脚本(该脚本会在keepalived.conf配置中被引用) [root@k8s-ha01 ~]# vim /opt/chk_nginx.sh #!/bin/bash counter=$(ps -ef|grep -w kube-nginx|grep -v grep|wc -l) if [ "${counter}" = "0" ]; then systemctl start kube-nginx sleep 2 counter=$(ps -ef|grep kube-nginx|grep -v grep|wc -l) if [ "${counter}" = "0" ]; then /etc/init.d/keepalived stop fi fi [root@k8s-ha01 ~]# chmod 755 /opt/chk_nginx.sh 3) 启动两个节点的keepalived服务 [root@k8s-ha01 ~]# /etc/init.d/keepalived start starting keepalived (via systemctl): [ ok ] [root@k8s-ha01 ~]# ps -ef|grep keepalived root 5358 1 0 00:32 ? 00:00:00 /usr/local/sbin/keepalived -d root 5359 5358 0 00:32 ? 00:00:00 /usr/local/sbin/keepalived -d root 5391 29606 0 00:32 pts/0 00:00:00 grep --color=auto keepalived 查看vip情况. 发现vip默认起初会在master节点上 [root@k8s-ha01 ~]# ip addr 1: lo: <loopback,up,lower_up> mtu 65536 qdisc noqueue state unknown group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens192: <broadcast,multicast,up,lower_up> mtu 1500 qdisc mq state up group default qlen 1000 link/ether 00:50:56:ac:3a:a6 brd ff:ff:ff:ff:ff:ff inet 172.16.60.247/24 brd 172.16.60.255 scope global ens192 valid_lft forever preferred_lft forever inet 172.16.60.250/32 scope global ens192 valid_lft forever preferred_lft forever inet6 fe80::250:56ff:feac:3aa6/64 scope link valid_lft forever preferred_lft forever 4) 测试vip故障转移 参考:https://www.cnblogs.com/kevingrace/p/6138185.html 当master节点的keepalived服务挂掉,vip会自动漂移到slave节点上 当master节点的keepliaved服务恢复后,从将vip资源从slave节点重新抢占回来(keepalived配置文件中的priority优先级决定的) 当两个节点的nginx挂掉后,keepaived会引用nginx监控脚本自启动nginx服务,如启动失败,则强杀keepalived服务,从而实现vip转移。
点击查看 kubernetes(k8s)容器集群管理环境完整部署详细教程-中篇
下一篇: 分布式架构设计分析