k8s kubernetes 核心笔记 镜像仓库 项目k8s改造(含最新k8s v1.16.2版本)

k8s kubernetes 核心笔记 镜像仓库 项目k8s改造

2019/10/24 Chenxin

一 基本资料

一 参考:
https://kubernetes.io/ 官网
https://kubernetes.io/docs/home/ 官网文档
<<kerbernetes 权威指南>> <<每天5分钟玩转k8s>> 图书
aws 和 阿里云 的k8s 集群,请参考对应云知识笔记内容.

二 说明:
docker内容请参考<<Docker 基础>>,<Dockerfile 以及 Docker compose>> 笔记
k8s yaml 配置文件请参考对应单独笔记内容.
k8s 文件说明请参考对应独立笔记内容.

二 k8s常用指令

kubectl get all    # 查看所有
kubectl get all --all-namespaces
kubectl get pods|deployments.|replicasets.|service -o wide
kubectl get nodes --show-labels    # 查看node标签

kubectl describe pods|deployments.|replicasets.|service xxx_name
kubectl apply -f xxx.yml

kubectl delete pods|deployments.|replicasets.|service xxx_name
kubectl delete -f xxx.yml

kubectl edit daemonsets. kube-proxy --namespace=kube-system    # 编辑内存里配置文件.应该是及时修改后生效,但测试并未生效
kubectl edit service nginx-svc

kubectl logs pod_name    # 查看pod内进程输出 (查看最后200行 kubectl logs --tail=200 pod_name 或最后1小时 --since=1h  )
kubectl api-versions    # 查看k8s当前支持的api版本
kubectl run busybox --rm -it --image=busybox /bin/sh    # 临时启用一个工具箱
kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword    # 临时启动一个mysql客户端
kubectl apply -f httpd.v1.yml --record    # 登记到revision,用于回滚
kubectl rollout history deployment httpd    # 查看历史记录,为回滚做准备
kubectl rollout undo deployment httpd --to-revision=1   # 回滚到版本1
kubectl exec mypod-id touch /mydata/hello    # 到Pod的container里执行指令
kubectl exec mypod-id df | grep data    # 到Pod里执行指令
kubectl create configmap game-config --from-file=docs/gameconfig/    # 目录内含有多个配置文件(每个配置文件里可以有很多内容)
journalctl -l -u kubelet    # 查看kubelet日志

kubectl get pods -o yaml     # 配置yaml格式详情
kubectl get endpoints    # 查看终端服务节点

故障排查主要使用
kubectl logs pod_name     # 查看控制台输出(pod运行的报错信息)
kubectl describe pods|deployments.|replicasets.|service xxx_name

kubectl top node node1 --v=8  #开启debug模式输出.使用top指令查看资源情况,需部署heapster(1.8之前版本默认提供.之后修改为service-metric).参考https://github.com/helm/charts/tree/master/stable/heapster

自动命令补全额外说明
具体请参考本文档"master命令自动补全"部分.如果失效,需要手动再次 source <(kubectl completion bash )

三 架构+安装服务(master+node)

架构说明
1个master,2个node(192.168为二次实验地址)
k8s-master 10.0.0.105 192.168.143.130
k8s-node1 10.0.0.106 192.168.143.131
k8s-node2 10.0.0.107 192.168.143.132

版本说明
首次测试为k8s的1.13版本,第二次测试为1.16.1版本

操作系统版本说明
k8s只支持centos7.x以上版本.
这里采用系统版本为:
AWS Marketplace的CentOS官方镜像而来(更新时间: 19/1/31) 默认为7.6版本(2018/10).
修改/etc/hostname

更新系统
yum update -y # 更新到最新系统版本

更新iptables(略过)
RHEL7.4无需更新iptables,因为iptables -V -> iptables v1.4.21

配置网络桥接与路由转发
/etc/sysctl.conf 文件增加

net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1

安装/启动docker

a.配置yum仓库

b.正式安装
yum install -y docker-ce
docker --version -> Docker version 18.09.2 (首次版本,第二次为Docker version 19.03.3, build a872fc2f86)

c.启动docker
systemctl start docker.service
systemctl enable docker.service

报错解决
Error: Package: 3:docker-ce-18.09.2-3.el7.x86_64 (docker-ce-stable) Requires: container-selinux >= 2.9
到centos官网寻找最新的container-selinux,然后安装,如下

yum install -y http://mirror.centos.org/centos/7/extras/x86_64/Packages/container-selinux-2.74-1.el7.noarch.rpm  

安装k8s的kubeadm, kubelet 和 kubectl
参考: https://kubernetes.io/zh/docs/setup/independent/install-kubeadm/

  • 国内-阿里云
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
  • 国外-配置google的源地址(也可以采用国内阿里云,中科大镜像)
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kube*
EOF

将 SELinux 设置为 permissive 模式(镜像默认已经将其禁用)
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

master+node安装
yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes # 会自动安装conntrack-tools cri-tools ebtables socat等依赖包

添加开机自启
systemctl enable docker.service # master+node都需要.
systemctl enable kubelet.service # master+node都需要.mster:先启动kubelet,通过它自启apiserver,controller-manager,scheduler.node:先kubelet后,自动通过kubelet启kube-proxy

到此可以重启一下系统.

四 创建集群

master初始化集群

1.绑定host

[root@master ~]# cat /etc/hostname 
master
[root@master ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.143.130 master
[root@master ~]# hostname
master

2.确保master上的docker已启动
ps axf|grep dockerd

3.(选做)可以将系统镜像里默认开启的rpc暂时关掉,并开机不启动
systemctl stop rpcbind.service rpcbind.socket
systemctl disable rpcbind.service rpcbind.socket

4.master上进行init

[root@ip-10-0-0-105 ~]# 
普通执行 kubeadm init --apiserver-advertise-address 10.0.0.105 --pod-network-cidr=10.244.0.0/16
或更改网络段  kubeadm init --apiserver-advertise-address 192.168.134.130 --pod-network-cidr=10.10.0.0/16
或指定k8s版本 kubeadm init --apiserver-advertise-address 192.168.134.130 --pod-network-cidr=10.10.0.0/16 --kubernetes-version=v1.16.1
或指定拉取国内镜像 kubeadm init --apiserver-advertise-address 192.168.134.130 --pod-network-cidr=10.10.0.0/16 --kubernetes-version=v1.16.1 --image-repository=registry.aliyuncs.com/google_containers

失败后的重新初始化(先复位 kubeadm reset) 
尝试1.kubeadm init --apiserver-advertise-address 192.168.134.130 --pod-network-cidr=10.10.0.0/16 --image-repository=registry.aliyuncs.com/google_containers 
尝试2.kubeadm init --apiserver-advertise-address 0.0.0.0 --pod-network-cidr=10.10.0.0/16 --image-repository=registry.aliyuncs.com/google_containers 

提示信息如下(正常执行状态)
[preflight] Running pre-flight checks    # kubeadm执行初始化前的检查
[certs] Using certificateDir folder "/etc/kubernetes/pki"    # 生成token和证书
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"    # 生成kubeconfig文件,kubelet需要这个文件与master通信
[apiclient] All control plane components are healthy after 21.506225 seconds    # 安装master组件,会从Google的Registry下载组件的Docker镜像.
[addons] Applied essential addon: CoreDNS    # 安装附加组件 kube-proxy和 kube-dns
Your Kubernetes master has initialized successfully!    # Kubernetes Master初始化成功

To start using your cluster, you need to run the following as a regular user:    # 提示如何配置kubectl
  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config
  
You should now deploy a pod network to the cluster.    # 提示如何安装Pod网络
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/
  
You can now join any number of machines by running the following on each node as root:    # 提示注册其他的node节点到这个cluster
  kubeadm join 10.0.0.105:6443 --token xj0xjp.d6r8fzuecc1ve0bu --discovery-token-ca-cert-hash sha256:cd505e2d2494e429cac81e50942c7c3d4eda50908fddccdb667aefccf0543517
或
  kubeadm join 192.168.143.130:6443 --token cgy56z.02yki9avqnukpxtg \
    --discovery-token-ca-cert-hash sha256:152fd4be14843846e48c6752b1bd003bf7fb5e56e22085b31002f6babd58a66f 

init报错收集(解决办法)

[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1
[ERROR Swap]: running with swap on is not supported. Please disable swap

a.需要关闭swap,"swapoff -a".
b.echo "1" >/proc/sys/net/bridge/bridge-nf-call-iptables (桥接模式/etc/sysctl.conf里net.bridge.bridge-nf-call-iptables = 1 ;如果系统无bridge-nf-call-iptables文件,则"modprobe br_netfilter"向内核中加载该模块).
c.国内访问,会自动docker pull k8s.gcr.io/xxx 需要FQ(也可以通过从国内pull后打tag的方式变相处理,具体请百度搜索).

5.执行init失败后采取的恢复方法
a.init执行失败,重新执行init的时候,需要添加 --ignore-preflight-errors=all ,这样会生成多个cluster(后期再删除)
b.执行 kubeadm reset 命令将主机恢复原状(注意,会将下载完的多个docker image也一并删除),然后重新执行 kubeadm init 命令再次进行init安装.

master配置kubectl
根据init初始化中的提示(root执行会有一些问题)这里创建一个centos用户来执行.
useradd centos
visudo里添加 centos ALL=(ALL) NOPASSWD: ALL # 涉及拷贝root权限的文件

拷贝配置文件
su - centos;
cd ~; mkdir .kube; sudo cp -i /etc/kubernetes/admin.conf .kube/config; sudo chown $(id -u)😒(id -g) .kube/config
如果希望kubectl命令可以在root下执行,如kubectl get nodes,则切换到root后,执行上面这行指令.否则k8s会提示拒绝连接.

master启用kubectl自动补全
a.bash客户端工具
在centos用户下,
cd ~;echo "source <(kubectl completion bash)" >> .bashrc
或如 . <(kubectl completion bash) 当然,也可以写入到/etc/profile里.退出当前终端,重启进去,以便加载source.

b.其他交互式 Kubernetes 客户端工具
参考 https://www.hi-linux.com/posts/44953.html
1.bash 略
2.ZSH-> echo "source <(kubectl completion zsh)" >> ~/.zshrc
3.Kube-prompt
4.Kube-shell
5.Kubectl Aliases

c.故障排查
在使用一段时间的kubectl命令自动补全后(安装k8s的metrics-server后),发现有些失效了
需要手动重新执行: source <(kubectl completion bash)
原因,未知,可能是被覆盖过.

master安装pod网络
在centos用户下,
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

master配置k8s自启动
systemctl list-unit-files |grep kubelet.service

node加入master
1.确保每个node上的docker已启动 -> ps axf|grep dockerd
2.master上查看token -> [root@ip-10-0-0-105 log]# kubeadm token list
3.修改hostname和hosts文件(类似master)
4.同样需要关闭swap,打开网络桥接(略,同master)
5.到各个node上,执行加入master指令

[root@ip-10-0-0-106 ~]# kubeadm join 10.0.0.105:6443 --token nc8...8zihj --discovery-token-ca-cert-hash  sha256:9d...a5
或 kubeadm join 192.168.143.130:6443 --token cgy56z.02yki9avqnukpxtg --discovery-token-ca-cert-hash sha256:152fd4be14843846e48c6752b1bd003bf7fb5e56e22085b31002f6babd58a66f
输出信息
...Run 'kubectl get nodes' on the master to see this node join the cluster.

6.开启开机自启动 -> systemctl enable kubelet.service
7.当master长时间运行后,加入新的node方法
重新获取token以及ca对应的hash值方法.默认token的有效期为24小时,当过期之后,该token就不可用了。解决方法,
a.重新生成新的token: kubeadm token create
b.查看token: kubeadm token list
c.获取ca证书sha256编码hash值:
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
d.节点重新加入集群:
kubeadm join 10.0.0.105:6443 --token cbdz...sj7 --discovery-token-ca-cert-hash sha256:ee...452d --ignore-preflight-errors=All

删除+清理节点
因加入node的hostname忘记修改了,故删掉重做.

1.master上操作
执行命令
kubectl drain localhost.localdomain --delete-local-data --force --ignore-daemonsets
kubectl delete node localhost.localdomain
检查命令
kubectl get nodes

2.node上操作
在master上面删除node并不会清理localhost.localdomain上面运行的容器,需要在删除节点上面手动运行清理命令
执行命令(重置所有kubeadm安装状态)
kubeadm reset
检查命令
按y重置状态,重置后docker ps检查容器消失

检查node,pod状态(master)
(会由于网络pull原因,需要等待一段时间才会正常显示如下)

[centos@master ~]$ kubectl get nodes 
NAME     STATUS   ROLES    AGE   VERSION
master   Ready    master   78m   v1.16.1
node1    Ready    <none>   33m   v1.16.1
node2    Ready    <none>   32m   v1.16.1

[centos@master ~]$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                          READY STATUS  RESTARTS IP              NODE   
kube-system   coredns-58cc8c89f4-4djwg      1/1   Running 0        10.10.0.2       master 
kube-system   coredns-58cc8c89f4-p9kmk      1/1   Running 0        10.10.0.3       master 
kube-system   etcd-master                   1/1   Running 0        192.168.143.130 master 
kube-system   kube-apiserver-master         1/1   Running 0        192.168.143.130 master 
kube-system   kube-controller-manager-master1/1   Running 1        192.168.143.130 master 
kube-system   kube-flannel-ds-amd64-8zqxr   1/1   Running 2        192.168.143.131 node1  
kube-system   kube-flannel-ds-amd64-mkrx6   1/1   Running 0        192.168.143.130 master 
kube-system   kube-flannel-ds-amd64-rvxr5   1/1   Running 0        192.168.143.132 node2  
kube-system   kube-proxy-cnqzl              1/1   Running 0        192.168.143.132 node2  
kube-system   kube-proxy-npwpj              1/1   Running 0        192.168.143.130 master 
kube-system   kube-proxy-prblt              1/1   Running 2        192.168.143.131 node1  
kube-system   kube-scheduler-master         1/1   Running 1        192.168.143.130 master 

到此,可以随意重启master 或 node 机器,而k8s集群都可以正常启动.

其他安装部署方式说明
1.RKE (单master)
使用Kubernetes的轻量级工具——Rancher Kubernetes Engine(RKE)进行Kubernetes的安装部署。RKE是一个用Golang编写的Kubernetes安装程序,相对于其它的工具来说,更加简单易用和上手。
具体请参考Rancher官网说明,或其他网上资源.
2.Sealos (多master)
多master,多node的安装方式.为k8s高可用方案之一.具体请参考本文档"高可用"部分.

五 k8s服务+应用部署流程

Master
Master上运行的Pod(或开放的Port)包括:
coredns 负责集群内的解析
etcd 服务发现机制(键值对存放).负责保存k8s Cluster的配置信息和各种资源状态信息.数据发生变化时,etcd会快速通知k8s的相关组件.
kube-apiserver 是供CLI或UI等外部组件访问Cluster的API.
kube-controller-manager 例如replication controller负责管理Deployment.
kube-scheduler 决定将Pod放到哪个Node上运行.
kube-proxy 通过proxy代理,将请求转发给对应的后端node的Pod
kube-flannel(Pod network) 可以提供Pod间相互通信的基础.比如flannel.

Node
Node上运行的Pod或Deamon/Port包括:
kubelet Node的agent,当Scheduler确定在哪个Node上运行Pod后,将Pod配置信息(image,volume)发给该节点kubelet.kubelet据此创建容器,并向Master报告状态.
kube-proxy service代表了后端的多个Pod,外界通过service访问Pod.service接收到的请求通过kube-proxy转发给Pod.若有多个副本,kube-proxy会实现负载均衡.
kube-flannel(Pod network) 可以提供Pod间相互通信的基础.比如flannel.

部署pod流程说明
master上执行:

kubectl run httpd-app --image=httpd --replicas=2
kubectl get deployments
kubectl get pods -o wide    # 会在master上显示各个pod的具体信息(包括对应node主机名和pod的IP)
NAME                        READY   STATUS    RESTARTS   AGE   IP           NODE            NOMINATED NODE   READINESS GATES
httpd-app-f9ccf4675-9zlkz   1/1     Running   0          31m   10.244.3.2   ip-10-0-0-107   <none>           <none>
httpd-app-f9ccf4675-tkvt5   1/1     Running   0          31m   10.244.1.2   ip-10-0-0-106   <none>           <none>

kubectl发送部署请求到api-server.
api-server通知controller-manager创建一个Deployment资源.
scheduler执行调度任务,将两个副本Pod分发到node1和node2.
node1和node2上的kubelet在各自的节点上创建并运行Pod.

补充说明
master上不会pull httpd的image,而由各个node去pull.然后在各自node上运行container.
应用的配置和当前状态信息保存在master的etcd中,执行kubectl get pods时,api-server会从etcd中读取.
flannel会为每个Pod都分配IP.因为没有创建service,所以目前kube-proxy还没有参与进来.

运行应用 controller-manager

k8s通过各种Controller来管理Pod的生命周期.为满足不同的业务场景,k8s具有以下几种controller:
Deployment,
ReplicaSet,
DaemonSet,
StatefuleSet,
Job.

进行run (Deployment)
kubectl run nginx-deployment --image=nginx:1.7.9 --replicas=2 # 等同于 kubectl run --generator=deployment/apps.v1 nginx-deployment --image=nginx:1.7.9 --replicas=2
提示:
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use
kubectl run --generator=run-pod/v1 or kubectl create instead.

k8s新版本可以用以下方式,然后再scale
kubectl create deployment --image=nginx:1.7.9 nginx-deployment
kubectl scale deployment nginx-deployment --replicas=2
注意,create一般是用于yml的配置文件.

换个频道,按照提示运行 kubectl run --generator=run-pod/v1 nginx-deployment --image=nginx:1.7.9 --replicas=2
这里replicas没有生效,只创建了1个pod.为什么?
没有再生产Deployment,而是直接创建了pod,为什么呢?
如何scale这种pod?
可以使用kubectl delete pods podname来删除创建的pod.

查看Deployment
[centos@ip-10-0-0-105 ~]$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 2/2 2 2 9m23s

查看Deployment详细信息
[centos@ip-10-0-0-105 ~]$ kubectl describe deployments

查看ReplicaSet
[centos@ip-10-0-0-105 ~]$ kubectl get replicasets

查看由哪个Deployment而来以及此pod创建过程
[centos@ip-10-0-0-105 ~]$ kubectl describe pods nginx-deployment-578fb949d8-2rgwj

以上流程的总结
用户通过kubectl 创建 Deployment.
Deployment 创建 ReplicaSet.
ReplicaSet 创建 Pod.
也可以看得出来,对象的命名方式是"子对象名字=父对象名字+随机字符串".这样,可以从Pod名字上追溯到ReplicaSet,再追溯到Deployment.

k8s master 与 node 上各配置文件解析说明

请参考<<k8s 软件配置文件解析>>笔记

k8s API用法查询 + YAML文件 + kubectl apply使用

具体参见<<k8s yml yaml 配置文件语法解析>>笔记
查看api版本
kubectl api-versions 查看当前k8s支持哪些api版本
kubectl explain Deployment.spec 查看用法/帮助手册(支持多个子项目,用"."表示)

示例

[centos@master deployment]$ cat nginx.yml 
apiVersion: apps/v1 # apiVersion是当前配置格式的版本
kind: Deployment    # kind 是要创建的资源类型, 这里是Deployment.
metadata:   # metadata 是该资源的元数据, name 是必须的元数据项.
  name: nginx-deployment
spec:   # spec 部分是该Deployment的规格说明.
  selector: #新加入部分
    matchLabels:
      app: nginx-deployment
  replicas: 2   # replicas 指明副本数量, 默认是1.
  template: # template 定义Pod 的模板, 这是配置文件的重要部分.
    metadata:   # metadata 定义Pod的元数据, 至少要定义一个label. label 的key和value 可以任意指定.
      labels:
        app: nginx-deployment
    spec:   # spec 描述 Pod的规格,此部分定义Pod中的每一个容器的属性, name 和 image 是必须的.
      containers:
      - name: nginx
        image: nginx:1.7.9
        #ports:
        #        #- containerPort: 80

创建
kubectl apply -f nginx.yml

查看
以下指令输出相同
kubectl get deployments # kubectl get deployments. 或kubectl get deployments.apps 或kubectl get deployments.extensions
kubectl get replicasets # kubectl get replica. replicasets.apps replicasets.extensions
kubectl describe deployments # kubectl describe deployments. deployments.apps deployments.extensions

删除
kubectl delete deployments. nginx-deployment
kubectl delete -f nginx.yml

伸缩
增加: 当前已经有2个pod处于running状态.想增加到5个.则修改 nginx.yml,将 replicas: 5 ,然后再次 kubectl apply -f nginx.yml
减少: 同上.

宕机
将node2关机,k8s会自动在node1上开启新的pod,以达到配置文件里的replicas数量.这个过程大约需要5分钟以上.
宕机的node会由 Ready 变成 NotReady (需要1分钟)
宕机的pod会由 running 变成 Terminating (需要5分钟)

恢复宕机
启动node2机器后,并不会将当前正常的pod再重新分一部分到node2上去.只有新的pod会发过去.
node2启动后,可能需要手动 systemctl start kubelet.service(在没有enable的情况下)

重启node1机器
因node1 reboot时间比较短(aws云主机),k8s在短时间内,并不会在node2上创建pod.而是node1重启后,其上的pod直接重启.kubectl get pods -o wide: node2上的3个pod的RESTARTS会由0->1.

用label控制Pod的位置

给对应node打个label: kubectl label nodes ip-10-0-0-106.ap-southeast-1.compute.internal disktype=ssd
查看label: kubectl get nodes --show-labels
修改nginx.yml配置文件,在containers同级,添加
nodeSelector:
disktype: ssd
应用配置: kubectl apply -f nginx.yml
确认全部pod在正确的node上产生: kubectl get pods -o wide
删除label: kubectl label nodes ip-10-0-0-106.ap-southeast-1.compute.internal disktype-
这时pod不会改变: kubectl get pods -o wide
即使重新apply也不会改变pod(因为没有合适的node可用,会提示"unchange"): kubectl apply -f nginx.yml; kubectl get pods -o wide
恢复nginx.yml配置文件: 注释掉修改部分
应用配置: kubectl apply -f nginx.yml; kubectl get pods -o wide # 已恢复至修改label前的状态.

尝试修改node1,2的主机名试试k8s是否异常
会异常,查看kubelet日志: journalctl -l -u kubelet

DaemonSet类型的controller说明

kubectl get pods --namespace=kube-system -o wide # -> 包括 kube-flannel-ds 和 kube-proxy

kube-flannel-ds
在部署pod网络的时候, 使用 kubectl apply -f https://....yml , 即使用的这个yml文件:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
从中可以看到 DaemonSet 的 kube-flannel 配置.

kube-proxy
由于无法拿到kube-proxy的yml配置文件,只能这样查看,
kubectl edit daemonsets. kube-proxy --namespace=kube-system

其实, k8s集群中的每个当前运行的资源,都可以通过 kubectl edit 查看配置和运行状态.比如,
kubectl edit deployments. nginx-deployment

Job类型的controller说明

Job即为工作类容器类型.
做完工作,就会自动退出.

cat myjob.yml
apiVersion: batch/v1    # 指明当前Job的apiVersion
kind:   Job    # 指明当前资源类型为 Job
metadata:
    name:   myjob
spec:
    template:
        metadata:
            name:   myjob
        spec:
            containers:
            - name: hello
              image: busybox
              command: ["echo", "hello k8s job!"]
            restartPolicy: Never    # restartPolicy 指定重启容器.对应Job,只能设置为 Never或者 OnFailure.对于其他controller,如Deployment,可以设置为Always.

kubectl apply -f myjob.yml
kubectl get job
kubectl get pods
kubectl get pods -o wide # 或 kubectl get pods --show-all
kubectl logs myjob-q8h86 # 查看job的输出"hello k8s job!"

若执行有问题,比如command: ["echoxxxx", "hello k8s job!"],k8s会一直尝试创建并执行pod(产生多个pod),排查,在Events里会有对应描述. -> kubectl describe pods myjob-4hrk5
如果将restartPolicy配置成OnFailure,k8s则只会尝试重启pod,不会创建新的.

Job的并行性: 通过 parallelism: 2 # 并行
Job配置总数量: 略
定时Job( CronJob ): 略

[centos@ip-10-0-0-105 ~]$ kubectl api-versions
admissionregistration.k8s.io/v1beta1
...
storage.k8s.io/v1beta1
v1

六 通过 Service (clusterIP) /Node:Port 访问 Pod 提供的服务

Service只是逻辑上的一个IP地址(绑定后端的多个Pod),并非物理实体(所以是无法ping通的).这点区别于Pod(可以ping通所有Pod的IP).

通过IP方式访问服务
kubernetes Service 从逻辑上代表了一组Pod,具体哪些pod则由label挑选.有了service,外部才有访问到Pod的途径(nodeIP+port对应serviceIP+port对应PodIP+port).

每个node有自己的物理IP.
每个service也有自己的IP(也就是ClusterIP),这个IP是不变的.客户端只需要访问Service的IP,K8S则负责建立和维护Service和Pod的映射关系.
每个pod都有自己的IP.Pod随时可以消亡,不能认为Pod是稳定的,而应该注重Service的稳定性.

K8S通过iptables来实现将 Pod+port映射到 -->ClusterIP+port (Service上) --> nodesIP+port (Node上).

目前kubernetes提供了两种负载分发策略:RoundRobin和SessionAffinity
RoundRobin:轮询模式,即轮询将请求转发到后端的各个Pod上
SessionAffinity:基于客户端IP地址进行会话保持的模式,第一次客户端访问后端某个Pod,之后的请求都转发到这个Pod上.默认是RoundRobin模式.
实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为 "ClientIP" (默认值为 "None")。

制作Deployment的yml

[centos@master httpd]$ cat  http.yml 
apiVersion: apps/v1 # apiVersion是当前配置格式的版本
kind: Deployment    # kind 是要创建的资源类型, 这里是Deployment.
metadata:   # metadata 是该资源的元数据, name 是必须的元数据项.
  name: httpd-deployment
spec:   # spec 部分是该Deployment的规格说明.
  selector: # 新加入部分,通过Label选择对应的pod
    matchLabels:
      app: httpd-pod
  replicas: 2   # replicas 指明副本数量, 默认是1.
  template: # template 定义Pod 的模板, 这是配置文件的重要部分.
    metadata:   # metadata 定义Pod的元数据, 至少要定义一个label. label 的key和value 可以任意指定.
      labels:
        #run: httpd-pod
        app: httpd-pod
    spec:   # spec 描述 Pod的规格,此部分定义Pod中的每一个容器的属性, name 和 image 是必须的.
      containers:
      - name: httpd
        image: httpd
        ports:
        - containerPort: 80
      #nodeSelector:
      #        #disktype: ssd

部署Deployment: kubectl apply -f http.yml

制作Service的yml

[centos@master httpd]$ cat http-svc.yml 
apiVersion: v1 # apiVersion是当前配置格式的版本
kind: Service    # kind 是要创建的资源类型
metadata:   # metadata 是该资源的元数据, name 是必须的元数据项.
  name: httpd-svc   # Service 的名字
spec:   # spec 部分是该Deployment的规格说明.
  type: NodePort # NodePort方式.还可以是ClusterIP,或者LoadBalancer
  selector:
    app: httpd-pod  # selector 指明挑选那些 label 为 app:httpd 的 Pod 作为 Service的后端
  ports:
  - protocol: TCP # 将service的8080端口映射到Pod的80端口,使用TCP协议
    nodePort: 30000 # 是各个节点上监听的端口(默认随机30000-32767)
    port: 8080  # 是ClusterIP上监听的端口(Service的)
    targetPort: 80  # 是Pod监听的端口

部署Service: kubectl apply -f httpd-svc.yml

查看Service与Pod的对应关系

[centos@ip-10-0-0-105 ~]$ kubectl describe service httpd-svc
Name:                     httpd-svc
Namespace:                default
...
Type:                     NodePort
IP:                       10.103.10.254
Port:                     <unset>  8080/TCP    # Service
TargetPort:               80/TCP    # Pod
NodePort:                 <unset>  30000/TCP    # Node
Endpoints:                10.244.1.17:80,10.244.2.29:80,10.244.2.30:80    # Pod IP+Pod port
...

在master和nodes上分别确认开放的kube-proxy端口(默认随机30000-32767)
sudo netstat -npl

访问service对应的port (目前只能本地可以访问,无法挂载外部EIP)
kubectl get service httpd-svc # 获取clusterIP -> 10.107.183.131
wget 10.103.10.254:8080 或 wget httpd-svc:8080 (这个需要在相同的namespace里才行)

访问node对应的port (目前唯一可以跨越互联网访问的方式)
wget 10.0.0.105:30000
wget 10.0.0.106:30000
wget 10.0.0.107:30000

访问pod对应的port (目前只能本地可以访问,无法挂载外部EIP)
kubectl get pods -o wide
wget 10.244.1.17
wget 10.244.2.29
wget 10.244.2.30

其中,在master和各个node物理机上,只能看到开放的30000端口,而8080和80都是看不到的(通过iptables内部映射的).

访问service的请求(访问clusterIP),会通过iptables将流量转发到后端的Pod.而且使用的是类似轮询的负载均衡策略.
Cluster的每个节点都配置了相同的iptables规则,这样就确保了整个cluster都能够通过service的clusterIP访问service.

固定service的IP地址

[centos@master nginx]$ cat nginx-svc.yml 
apiVersion: v1 # apiVersion是当前配置格式的版本
kind: Service    # kind 是要创建的资源类型
metadata:   # metadata 是该资源的元数据, name 是必须的元数据项.
  name: nginx-svc   # Service 的名字
spec:   # spec 部分是该Deployment的规格说明.
  type: NodePort # NodePort方式.还可以是ClusterIP,或者LoadBalancer
  selector:
    #run: httpd  # selector 指明挑选那些 label 为 run:httpd 的 Pod 作为 Service的后端
    app: nginx-deployment2  # selector 指明挑选那些 label 为 run:httpd 的 Pod 作为 Service的后端
  clusterIP: 10.108.72.147
  ports:
  - protocol: TCP # 将service的8080端口映射到Pod的80端口,使用TCP协议
    nodePort: 30000 # 是各个节点上监听的端口(默认随机30000-32767)
    port: 8080  # 是ClusterIP上监听的端口(Service的)
    targetPort: 80  # 是Pod监听的端口

在 selector 同级 clusterIP: 10.108.72.147 ,指定service的IP地址.
也可以指定为其他网段,只要和Pod不冲突就可以,如 clusterIP: 10.101.72.10 .

通过DNS方式访问 Service
这里需要跟对应的Service在同一个namespace里才行.在物理机上(master和nodes)是无法解析的.
以下是在相同namespace里运行的busybox访问service和pod服务
[centos@ip-10-0-0-105 ~]$ kubectl run busybox --rm -it --image=busybox /bin/sh

在busybox里,可以通过的方式
wget 10.0.0.105:30000 # 访问master或nodes物理IP地址
wget 10.103.10.254:8080 # 访问ClusterIP(虚拟IP),也就是service的IP
wget httpd-svc:8080 # 访问service的NAME.也可以httpd-svc.default:8080
wget 10.244.2.30 # 访问Pod的IP

在busybox里,无法通过的方式
wget httpd-8c6c4bd9b-5rjz8 # 访问Pod的NAME

其他略.

外网访问Service
1.ClusterIP
只有cluster内部可以访问service.是默认类型.
2.NodePort
配置文件里需要 type: NodePort,通过cluster节点的静态端口对外提供服务.cluster外部可以通过 NodeIP:NodePort方式访问service.
3.LoadBalance
service里有cloud provider特有的load balancer对外提供服务.比如AWS等.

报错/问题处理
报错1.
tailf /var/log/messages
Oct 18 14:44:15 master kubelet: E1018 14:44:15.594052 736 summary_sys_containers.go:47] Failed to get system container stats for "/system.slice/docker.service": failed to get cgroup stats for "/system.slice/docker.service": failed to get container info for "/system.slice/docker.service": unknown container "/system.slice/docker.service"
解决方法:
1.修改kubelet配置文件 /lib/systemd/system/kubelet.service.d/10-kubeadm.conf ,添加一行
Environment="KUBELET_CGROUP_ARGS=–cgroup-driver=systemd –runtime-cgroups=/systemd/system.slice –kubelet-cgroups=/systemd/system.slice"
2.重启kubelet: systemctl restart kubelet

报错2
在pod和service都已经启动后,wget/telnet访问不了以下地址
物理机 192.168.143.130/131/132:30000
Service 10.108.72.146:8080
Pod 10.10.2.12/10.10.3.13:80

原因,iptables -L (无转发策略).

iptables -L
正确的:
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
KUBE-FORWARD  all  --  anywhere             anywhere             /* kubernetes forwarding rules */
KUBE-SERVICES  all  --  anywhere             anywhere             ctstate NEW /* kubernetes service portals */
DOCKER-ISOLATION-STAGE-1  all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  10.244.0.0/16        anywhere            
ACCEPT     all  --  anywhere             10.244.0.0/16       

错误的:    Chain FORWARD (policy DROP) ...

解决方式2种:
1.配置iptables转发: (无需重启服务,只是临时生效,重启后会被还原)
iptables -P FORWARD ACCEPT
2.修改/etc/sysctl.conf(需要重启机器,否则手动逐条修改iptables的FORWARD链里的多条规则)
net.ipv4.ip_forward = 1

七 关于资源(限制资源申请,查看资源占用,改变资源标签)

控制pod申请的资源大小
采自阿里云ACK文档 "容器服务Kubernetes版 > 最佳实践 > 集群 > Kubernetes集群选型及高可靠推荐配置 > 高可靠推荐配置 ->声明每个Pod的resource"
下面的例子中,声明Nginx这个Pod需要1核CPU,256M的内存,运行中实际使用不能超过2核CPU和512M内存。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    resources: # 资源声明
      requests:
        memory: "256Mi"
        cpu: "1000m"
      limits:
        memory: "512Mi"
        cpu: "2000m"
若node是单核,则Pod会一直处于Pending状态,原因 kubectl describe pods nginx-deployment-775c7756c6-mbvsz,报错为
1 node(s) had taints that the pod didn't tolerate, 2 Insufficient cpu.(node节点的CPU数量不足).
正常的话,kubectl apply -f nginx.yml 会先新建新的pod,然后删除旧的pod.

labels改变
labels改变,需要先kubectl delete -f xxx.yml.而不能直接kubectl apply -f xxx.yml.

kubectl top 查看node/pod等的cpu,内存占用情况
kubernetes 从1.8版本开始不再集成cadvisor,也废弃了heapster,使用metrics server来提供metrics.
官网: https://github.com/kubernetes-incubator/metrics-server/ 下载zip文件.解压至任意目录.

unzip metrics-server-master.zip
kubectl apply -f metrics-server-master/deploy/1.8+/ #需要等待大约2分钟

需要修改 deploy/1.8+/metrics-server-deployment.yaml 文件,添加以下参数(否则kubectl top node node2 会报Error from server (NotFound): nodemetrics.metrics.k8s.io "node2" not found)
...
      containers:
      - name: metrics-server
        image: k8s.gcr.io/metrics-server-amd64:v0.3.6
        args:   #这里加入args参数
        - --kubelet-insecure-tls
        - --kubelet-preferred-address-types=InternalIP

注意,如果metrics-server的pod会报访问10.96.0.1 443超时(通过kubectl logs pod-name指令,有报错dial tcp 10.96.0.1:443: i/o timeout),原因是node节点的iptables里没有对应的SNAT(MASQUERADE),具体参考"关于网络"部分.

八 关于网络

K8S采用的是扁平化的网络模型,每个Pod都有自己的IP,并且可以直接通信.可以被其他Pod或node节点ping通.而Service是逻辑上的IP,不能被ping通.
CNI规范使得K8S可以灵活选择多种Plugin实现集群网络.
Network Policy赋予了K8S强大的网络访问控制机制(可以不用这个,直接用云平台的访问控制就可以了).

1.pod访问外部地址空间
1.处于default命名空间的Pod busybox 10.10.3.32 且落在了node1上(192.168.143.131).busybox访问其他IP测试.
a.可以访问:
10.10.3.21 80
10.10.2.32 80
10.101.72.10 8080 nginx-svc
10.96.0.10 53
10.10.0.10 53
10.10.0.11 53
192.168.143.131 30000 因为busybox在node1上.
b.不能访问:
192.168.143.130 30000
192.168.143.132 30000
10.96.0.1 443
192.168.143.130 6443
8.8.8.8

2.节点机器 192.168.143.130/131/132
a.可以访问:全部
b.不能访问:无

原因是node1节点的iptables默认没有开启Pod对外访问的MASQUERADE.
在192.168.143.131(node1)上执行

iptables -t nat -I POSTROUTING -s 10.10.0.0/16 -j MASQUERADE

这样busybox就可以访问之前访问不了的那些地址空间了(包括外网地址,如8.8.8.8).
查看当前iptables的nat表:

iptables -nL -t nat (查看nat表的所有链)
iptables -nL POSTROUTING -t nat (指定POSTROUTING链)

2.关于节点iptables的NAT
a.SNAT
SNAT是source NAT源地址目标转换(如ADSL家用路由器多台电脑上网,服务器接收到的IP地址不是电脑的内网IP,而是路由器的出口IP).

b.DNAT
DNAT是destination NAT目标网络地址转换(内网提供的服务器,对外提供服务.如员工通过外部访问公司内服务器的VPN/WEB等.路由器将目标地址转换为公司内网服务器IP).

c.MASQUERADE
MASQUERADE,地址伪装,是SNAT的一个特例.

SNAT是指在数据包从网卡发送出去的时候,把数据包中的源地址部分替换为指定的IP,这样,接收方就认为数据包的来源是被替换的那个IP的主机。
MASQUERADE是用发送数据的网卡上的IP来替换源IP,因此,对于那些IP不固定的场合,比如拨号网络或者通过dhcp分配IP的情况下,就得用MASQUERADE。
DNAT,就是指数据包从网卡发送出去的时候,修改数据包中的目的IP,表现为如果你想访问A,可是因为网关做了DNAT,把所有访问A的数据包的目的IP全部修改为B,那么,你实际上访问的是B。

d.在哪个链上执行
因为,路由是按照目的地址来选择的,因此,DNAT是在PREROUTING链上来进行的,而SNAT是在数据包发送出去的时候才进行,因此是在POSTROUTING链上进行的

e.地址/网卡接口说明
但使用SNAT的时候,出口ip的地址范围可以是一个,也可以是多个,例如:

如下命令表示把所有10.8.0.0网段的数据包SNAT成192.168.5.3的ip然后发出去
iptables -t nat -A POSTROUTING -s 10.8.0.0/255.255.255.0 -o eth0 -j SNAT –to-source 192.168.5.3

如下命令表示把所有10.8.0.0网段的数据包SNAT成192.168.5.3/192.168.5.4/192.168.5.5等几个ip然后发出去
iptables -t nat -A POSTROUTING -s 10.8.0.0/255.255.255.0 -o eth0 -j SNAT –to-source 192.168.5.3-192.168.5.5

这就是SNAT的使用方法,即可以NAT成一个地址,也可以NAT成多个地址.

但是,对于SNAT,不管是几个地址,必须明确的指定要SNAT的ip(MASQUERADE则无需指定).
假如当前系统用的是ADSL动态拨号方式,那么每次拨号,出口ip192.168.5.3都会改变,而且改变的幅度很大,不一定是192.168.5.3到192.168.5.5范围内的地址.这个时候如果按照现在的方式来配置iptables就会出现问题了.因为每次拨号后,服务器地址都会变化,而iptables规则内的ip是不会随着自动变化的.每次地址变化后都必须手工修改一次iptables,把规则里边的固定ip改成新的ip.这样是非常不好用的.

MASQUERADE就是针对这种场景而设计的,他的作用是,从服务器的网卡上,自动获取当前ip地址来做NAT
比如下边的命令:

iptables -t nat -A POSTROUTING -s 10.8.0.0/255.255.255.0 -o eth0 -j MASQUERADE 或
iptables -t nat -I POSTROUTING -s 10.10.0.0/16 -j MASQUERADE

如此配置的话,不用指定SNAT的目标ip了.不管现在eth0的出口获得了怎样的动态ip,MASQUERADE会自动读取eth0现在的ip地址然后做SNAT出去.
这样就实现了很好的动态SNAT地址转换.

f.示例说明
我们可能需要将访问主机的7979端口映射到8080端口。iptables重定向:
iptables -t nat -A PREROUTING -p tcp --dport 7979 -j REDIRECT --to-ports 8080

将外网访问192.168.75.5的80端口转发到192.168.75.3:8000端口。
iptables -t nat -A PREROUTING -d 192.168.75.5 -p tcp --dport 80 -j DNAT --to-destination 192.168.75.3:8000

将192.168.75.3 8000端口将数据返回给客户端时,将源ip改为192.168.75.5
iptables -t nat -A POSTROUTING -d 192.168.75.3 -p tcp --dport 8000 -j SNAT 192.168.75.5

3.CNI
CNI是Container Network Interface的是一个标准的,通用的接口。现在容器平台:docker,kubernetes,mesos,容器网络解决方案:flannel,calico,weave。只要提供一个标准的接口,就能为同样满足该协议的所有容器平台提供网络功能,而CNI正是这样的一个标准接口协议。

CNI用于连接容器管理系统和网络插件。提供一个容器所在的network namespace,将network interface插入该network namespace中(比如veth的一端),并且在宿主机做一些必要的配置(例如将veth的另一端加入bridge中),最后对namespace中的interface进行IP和路由的配置。

CNI的工作是从容器管理系统处获取运行时信息,包括network namespace的路径,容器ID以及network interface name,再从容器网络的配置文件中加载网络配置信息,再将这些信息传递给对应的插件,由插件进行具体的网络配置工作,并将配置的结果再返回到容器管理系统中。

目录与文件说明
CNI插件是可执行文件,会被kubelet调用。
启动kubelet --network-plugin=cni,--cni-conf-dir 指定networkconfig配置,默认路径是:/etc/cni/net.d,并且,--cni-bin-dir 指定plugin可执行文件路径,默认路径是:/opt/cni/bin;

常见问题
CNI 网络插件配置错误,导致多主机网络不通,比如:IP 网段与现有网络冲突/插件使用了底层网络不支持的协议/忘记开启 IP 转发等.
sysctl net.ipv4.ip_forward
sysctl net.bridge.bridge-nf-call-iptables

九 更新 与 回滚(1.16版本未测试)

更新回滚采用的是渐进式的方式逐步替换掉旧版本Pod(默认是一个一个替换,可以通过K8S参数maxSurge和maxUnavaiable来精细控制Pod的替换数量).
如果更新不如预期期望,可以回滚.

[centos@ip-10-0-0-105 ~]$ cat httpd.v1.yml
apiVersion: apps/v1beta1 # apiVersion是当前配置格式的版本
kind: Deployment    # kind 是要创建的资源类型, 这里是Deployment.
metadata:   # metadata 是该资源的元数据, name 是必须的元数据项.
  name: httpd
spec:   # spec 部分是该Deployment的规格说明.
  revisionHistoryLimit: 10  # 默认配置下,K8S只会保留最近的几个revision.
  replicas: 3   # replicas 指明副本数量, 默认是1.
  template: # template 定义Pod 的模板, 这是配置文件的重要部分.
    metadata:   # metadata 定义Pod的元数据, 至少要定义一个label. label 的key和value 可以任意指定.
      labels:
        run: httpd
    spec:   # spec 描述 Pod的规格,此部分定义Pod中的每一个容器的属性, name 和 image 是必须的.
      containers:
      - name: httpd
        image: httpd:2.4.16
        ports:
        - containerPort: 80

httpd.v2.yml    # image: httpd:2.4.17
httpd.v3.yml    # image: httpd:2.4.18

更新
kubectl apply -f httpd.v1.yml --record
kubectl get deployments. httpd -o wide # 查看

kubectl apply -f httpd.v2.yml --record # 更新到v2
kubectl apply -f httpd.v3.yml --record # 更新到v3

回滚
kubectl rollout history deployment httpd # 查看历史记录
kubectl rollout undo deployment httpd --to-revision=1 # 回滚到历史记录里的哪个版本

kubectl get deployments. httpd -o wide
kubectl rollout history deployment httpd # 查看历史记录(发生变化)

十 健康检查 Health Check

自愈的默认实现方式是自动重启发生故障的容器.
除此之外,用户还可以利用Liveness 和 Readiness 探测机制设置更精细的健康检查,进而实现:
零停机部署.
避免部署无效的镜像.
更加安全的滚动升级.

默认的健康检查
每个容器启动时都会执行一个进程,该进程由Dockerfile的CMD或ENTRYPOINT指定.如果进程退出时返回码非零,则认为容器发生故障,K8S就会根据restartPolicy重启容器.

示例,创建Pod,如下

[centos@ip-10-0-0-105 ~]$ cat healthcheck.yml
apiVersion: v1 # apiVersion是当前配置格式的版本
kind: Pod    # kind 是要创建的资源类型
metadata:   # metadata 是该资源的元数据, name 是必须的元数据项.
  name: healthcheck   # 名字
  labels:
    test: healthcheck
spec:   # spec 规格说明.
  restartPolicy: OnFailure
  containers:
  - name: healthcheck
    image: busybox
    args:
    - /bin/sh
    - -c
    - sleep 10;exit 1
kubectl apply -f healthcheck.yml    # 部署
kubectl get pods    # 会看到不断的变化
kubectl delete -f healthcheck.yml    # 删除

缺点: 只能简单判断.但比如web服务器返回500这类,就不行了.就需要 Liveness 探测.

Liveness探测
功能:
健康的时候就不重启(活着的时候).Liveness探测告诉K8S什么时候通过重启容器来实现自愈.

[centos@ip-10-0-0-105 ~]$ cat liveness.yml
apiVersion: v1 # apiVersion是当前配置格式的版本
kind: Pod    # kind 是要创建的资源类型
metadata:   # metadata 是该资源的元数据, name 是必须的元数据项.
  name: liveness   # 名字
  labels:
    test: liveness
spec:   # spec 规格说明.
  restartPolicy: OnFailure
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:    # 配置文件
      exec:
        command:
        - cat
        - /tmp/healthy  # 如果该文件存在,则认为容器正常.返回值非0,就认为异常.然后重启
      initialDelaySeconds: 10   # 指定容器启动多少秒后开始执行Liveness探测.具体看启动服务需要花费的时间.
      periodSeconds: 5  # 指定每隔几秒探测1次.K8S如果连续执行3次探测是吧,则会杀掉并重启容器.

部署    kubectl apply -f liveness.yml
确认    kubectl get pods
查看日志    kubectl describe pod liveness
删除    kubectl delete -f liveness.yml

Readiness探测
功能:
健康的时候(准备就绪了),就放到service里.Readiness探测告诉K8S什么时候可以将容器加入到Service负载均衡池中对外提供服务.
Readiness语法与Liveness完全一样.对应将yml里的所有liveness全部替换为readiness.

查看日志 kubectl describe pod readiness

对Liveness 和 Readiness 探测比较
1.Liveness和Readiness是两种Health Check机制,如果不特意配置,K8S将对两种探测采取相同的默认行为,即通过判断容器启动进程的返回值是否为0来判断探测是否成功.
2.两种探测的配置方法完全一样.不同之处是: Liveness探测会重启容器,Readiness探测则是将容器设置为不可用,不接收Service转发的请求.
3.Liveness 和 Readiness 探测是独立执行的,二者之间没有依赖,所以可以单独使用,也可以同时使用.

Health Check 在Scale Up中的应用
通过Readiness判断容器是否就绪(一般容器初始化会花时间,不能启动立刻提供服务),避免将请求发送给未就绪的backend.
配置,略(健康检查会涉及到用户代码部分)
...
readinessProbe:
httpGet:
scheme: HTTP
path: /healthy # 用户自己实现
port: 8080
inittialDeploySeconds: 10
periodSeconds: 5
...

Health Check 在滚动更新中的应用
另一个场景就是Rolling Update.具体略.
参数说明

...# 假设10个副本
spec:
  strategy:
    rolingUpdate:
      maxSurge: 25%    # 控制滚动更新过程中,副本总数最大值不超过多少.公式: roundUp(10+10*35%)=13(向上取整)
      maxUnavailable: 25%    # 控制滚动更新过程中,不可用副本比例,向下取整.
...

滚动失败,可以通过 kubectl rollout undo 回滚到上一个版本.

十一 数据管理

概述
K8S如何管理存储资源
emptyDir和hostPath类型的Volume使用最方便,但可持久性不强.
K8S支持多种外部存储系统的Volume.
PV和PVC分离了管理员和普通用户的职责,更适合生产环境.
StorageClass实现更高效的动态供给.

Volume
容器和Pod是短暂的.
Volume的生命周期独立于容器(注意,不是Pod).
当Volume被mount到Pod,Pod中的所有容器都可以访问这个Volume.
K8s Volume支持多种backend类型,包括
emptyDir
hostPath
GCE Persistent Disk
AWS Elastic Block Store
NFS
Ceph
...
对于容器来说,所有类型的Volume都只是一个目录.

emptyDir
是Host上的一个空目录.对于容器来说是持久的,对于Pod则不是.也就是emptyDir Volume的生命周期与 Pod一致.
yml文件

[centos@ip-10-0-0-105 ~]$ cat emptyDir.yml
apiVersion: v1
kind: Pod
metadata:
  name: producer-consumer
spec:
  containers:
  - image: busybox
    name: producer
    volumeMounts:
    - mountPath: /producer_dir  # producer容器将 shared-volume 到 /producer_dir目录
      name: shared-volume
    args:
    - /bin/sh   # producer容器通过echo将数据写入到文件hello里
    - -c
    - echo "hello world" > /producer_dir/hello ; sleep 30000
  - image: busybox
    name: consumer
    volumeMounts:
    - mountPath: /consumer_dir  # consumer容器将shared-volume 到 /consumer_dir目录
      name: shared-volume
    args:
    - /bin/sh   # consumer容器通过cat从文件hello读数据
    - -c
    - cat /consumer_dir/hello ; sleep 30000
  volumes:
  - name: shared-volume # 定义了一个emptyDir类型的 Volume shared-volume
    emptyDir: {}

部署
kubectl apply -f emptyDir.yml

查看
kubectl logs producer-consumer consumer

到对应的host上查看volume绑定的目录是否一致
docker inspect producer_id
docker inspect consumer_id

emptyDir是Host上创建的临时目录,其优点是能够方便的为Pod中的容器提供共享存储,不需要额外配置.
它不具备持久性,如果Pod不存在了,emptyDir也就没有了.根据这个特性,emptyDir特别适合Pod中的容器需要临时共享存储空间的场景,比如上面的生产者消费者用例.

hostPath
hostPath Volume 的作用是将Docker Host文件系统中的已经存在的目录 mount给Pod的容器.一般都不要这么干,会增加Pod与节点的耦合,限制了Pod的使用.
不过那些需要访问K8s或docker内部数据(配置文件和二进制库)的应用则需要使用hostPath.

外部 Storage Provider
使用云盘方式.
也可以使用主流的分布式存储,比如Ceph, GlusterFS等.

可持久化 PersistentVolume 与 PersistentVolumeClaim
PersistentVolume(PV)是外部存储系统中的一块存储空间,由系统管理员创建和维护.
PersistentVolumeClaim(PVC)是对PV的申请.由普通用户创建和维护.用户创建PVC后,由K8S查找并提供满足条件的PV.
有了PV和PVC,开发人员和运维人员才能降低耦合,提高效率和安全.

NFS PV
NFS搭建请参考<<NFS 文件服务器搭建>>
在NFS上创建目录
mkdir -p /nfsdata/pv1/
在NFS上配置目录
[root@ip-10-0-0-104 nfsdata]# cat /etc/exports
/nfsdata/ 10.0.0.0/24(rw,sync,no_root_squash,no_all_squash)
重启NFS服务 systemctl restart nfs
确认 showmount -e localhost

创建PV

[centos@ip-10-0-0-105 nfs]$ cat nfs-pv1.yml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv1
spec:
  capacity:
    storage: 1Gi    # capacity指定PV的容量为1GB
  accessModes:
    - ReadWriteOnce # ReadWriteOnce,ReadOnlyMany,ReadWriteMany 3种.Once表示单个节点
  persistentVolumeReclaimPolicy: Recycle    # PV的回收策略.Retain表示管理员手动回收.Recycle表示清除PV中的数据,同rm -rf /thevolume/*.Delete表示删除Storage Provider上对应的存储资源,例如AWS EBS.
  storageClassName: nfs # 指定PV的class为nfs.相当于为PV设置了一个分类,PVC可以指定class申请相应的class的PV.
  nfs:
    path: /nfsdata/pv1  # 指定PV在NFS服务器上对应的目录
    server: 10.0.0.104

部署并查看
kubectl apply -f nfs-pv1.yml
kubectl get pv

创建PVC

[centos@ip-10-0-0-105 nfs]$ cat nfs-pvc1.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi    # PV和PVC仅仅是通过这个大小来进行匹配.
  storageClassName: nfs

PVC只需要指定PV的容量,访问模式,class即可.

部署并查看
kubectl apply -f nfs-pvc1.yml
kubectl get pvc
kubectl get pv
确认mypvc1已经Bound到mypv1上.

创建Pod

[centos@ip-10-0-0-105 nfs]$ cat pod1.yml
apiVersion: v1
kind: Pod
metadata:
  name: mypod1
spec:
  containers:
    - name: mypod1
      image: busybox
      args:
      - /bin/sh
      - -c
      - sleep 30000
      volumeMounts:
      - mountPath: "/mydata"
        name: mydata
  volumes:
    - name: mydata
      persistentVolumeClaim:
        claimName: mypvc1

与使用普通Volume的格式类似,在volumes中通过persistentVolumeClaim指定使用mypvc1申请Volume.

部署并查看
kubectl apply -f pod1.yml
kubectl get pods -o wide

测试
kubectl exec mypod1 touch /mydata/hello
到nfs服务器上,对应目录下,确认该文件是否已经生成.

遇到的问题:
在apply的时候,pod创建一直处于ContainerCreating状态.使用describe确认故障缘由:
kubectl describe pod PodName
提示mounting 10.0.0.104:/nfsdata/pv1 failed,那么可以确定是因为NFS服务器上没有/nfsdata/pv1/目录导致.手动到NFS上去创建即可.

回收PV
删除pod,删除pvc,删除pv.
pv中的删除策略目前是Recycle,表示删除pv的时候,数据也会被删除.修改为persistentVolumeReclaimPolicy: Retain,删除pv后,不会删除数据.数据可以手动到nfs服务器上删除.

PV动态供给(暂未测试)
静态供给: 创建PV,创建PVC,创建Pod
动态供给: 如果没有满足PVC条件的PV,则动态创建PV.这样就不需要提前创建PV了.动态供给通过StorageClass实现.

[centos@ip-10-0-0-105 nfs]$ cat cloud-pv1.yml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
reclaimPolicy: Retain

[centos@ip-10-0-0-105 nfs]$ cat cloud-pvc1.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: standard

mysql示例
模拟一个node宕机,新的pod依然通过volume实现数据可持久化.

编写PV

[centos@ip-10-0-0-105 mysql-nfs]$ cat mysql-pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  capacity:
    storage: 1Gi    # capacity指定PV的容量为1GB
  accessModes:
    - ReadWriteOnce # ReadWriteOnce,ReadOnlyMany,ReadWriteMany 3种.Once表示单个节点
  persistentVolumeReclaimPolicy: Retain    # PV的回收策略
  storageClassName: nfs # 指定PV的class为nfs.相当于为PV设置了一个分类,PVC可以指定class申请相应的class的PV.
  nfs:
    path: /nfsdata/mysql-pv # 指定PV在NFS服务器上对应的目录
    server: 10.0.0.104

编写PVC

[centos@ip-10-0-0-105 mysql-nfs]$ cat mysql-pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs

部署PV,PVC

kubectl apply -f mysql-pv.yml
kubectl apply -f mysql-pvc.yml
kubectl get pv,pvc

编写Service和Deployment

[centos@ip-10-0-0-105 mysql-nfs]$ cat mysql.yml
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
  selector:
    app: mysql

---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pvc

部署

kubectl apply -f  mysql.yml

启动一个临时mysql client pod 测试

kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword
mysql> use mysql
mysql> create table my_id( id int(4) );
mysql> insert my_id values( 111 );
mysql> select * from my_id;

到pod所在node上,shutdown该node.等待5-10分钟后,新的pod会在另一个node上产生.
查看mysql数据是否持久化的:

kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword
mysql> use mysql
mysql> select * from my_id;

十二 Secret 与 Configmap

介绍如何向Pod传递配置信息.如果信息需要加密,可以使用Secret;如果是一般的配置信息,可以使用ConfigMap.
Secret和ConfigMap支持4种定义方法.Pod在使用他们时,可以选择Volume方式或者是环境变量方式,不过只有Volume方式支持动态更新(动态更新大约延迟几十秒).
生产环境若对安全要求没那么高,建议用ConfigMap.

创建Secret
1.通过原始的方式 --from-literal
kubectl create secret generic mysecret --from-literal=useranme=admin --from-literal=password=123456
kubectl get secret

2.通过文件方式 --from-file
echo -n admin > ./username
echo -n 123456 > ./password
kubectl create secret generic mysecret2 --from-file=./username --from-file=./password
kubectl get secrets

3.通过变量方式 --from-env-file
[centos@ip-10-0-0-105 Secret-ConfigMap]$ cat env.txt # 文件env.txt每行Key=Value对应一个信息条目
useranme=admin
password=123456
应用一下 kubectl create secret generic mysecret3 --from-env-file=env.txt
kubectl get secrets

4.通过YAML配置文件

echo -n admin| base64
echo -n 123456|base64
[centos@ip-10-0-0-105 Secret-ConfigMap]$ cat mysecret.yml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret4
data:
  username: YWRtaW4=
  password: MTIzNDU2
应用一下    kubectl apply -f mysecret.yml
kubectl get secrets

查看Secret

kubectl get secrets    # 能显示内部data数据条目个数
kubectl describe secrets mysecret[1-4]    # 显示Data    ====    password:  6 bytes    useranme:  5 bytes 看不到具体数据
kubectl edit secrets mysecret[1-4]    # 显示base64加密后的 data:      password: MTIzNDU2      username: YWRtaW4=
要查看明文,可以--decode一下,如下
echo -n MTIzNDU2|base64 --decode    和    echo -n YWRtaW4=|base64 --decode

在Pod中使用Secret
volume方式
以Volume方式使用的Secret支持动态更新,Secret更新后,容器中的数据也会更新.

[centos@ip-10-0-0-105 Secret-ConfigMap]$ cat mypod.yml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: busybox
    args:
      - /bin/sh
      - -c
      - sleep 10; touch /tmp/healthy; sleep 30000
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"    # 将foo mount到容器的/etc/foo目录,可以指定读写权限为readOnly
      readOnly: true
  volumes:
  - name: foo    # 定义volume foo,来源secret mysecret
    secret:
      secretName: mysecret

验证: kubectl exec -it mypod sh # 可以查看/etc/foo下的文件内容.文件username和password内容都是明文.

也可以修改路径

mypod.yml
...
  volumes:
  - name: foo
    secret:
      secretName: mysecret2    # 注意,这里不能用"1"那种方式了,可以2-4.推荐4的方式,以便Secret更新后,一分钟左右,容器中的数据也随之更新
      items:    # 修改路径,文件会放置到/etc/foo/my-group/my-username
      - key: username
        path: my-group/my-username    
      - key: password
        path: my-group/my-password

环境变量方式
通过Volume使用Secret,容器必须从文件读取数据,稍显麻烦,K8S还支持通过环境变量使用Secret.缺点是无法支持Secret动态更新.
具体请参考相关资料

ConfigMap(主要使用2,4这两种方式)
区别于Secret,用于非敏感信息.比如配置文件.
使用方式与Secret类似.

创建方式4种

第一种
kubectl create configmap myconfigmap --from-literal=config1=xxx --from-literal=config2=yyy
第二种    从文件或含有多个文件的目录
echo -n xxx > ./config1
echo -n yyy > ./config2
kubectl create configmap myconfigmap2 --from-file=./config1 --from-file=./config2    # 文件
kubectl create configmap game-config --from-file=docs/gameconfig/    # 目录内含有多个配置文件(每个配置文件里可以有很多内容)
第三种
[centos@ip-10-0-0-105 Secret-ConfigMap]$ cat env2.txt
config1=xxx
config2=yyy
kubectl create configmap myconfigmap3 --from-env-file=env2.txt
第四种 yml文件
[centos@ip-10-0-0-105 Secret-ConfigMap]$ cat myconfigmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myconfigmap4
data:
  config1: xxx    # 这里可以嵌套实现一个文件里多对键值.还可以有多个文件.
  config2: yyy
kubectl apply -f myconfigmap.yml

查看
kubectl get configmaps

编写pod文件

[centos@ip-10-0-0-105 Secret-ConfigMap]$ cat mypod2.yml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: busybox
    args:
      - /bin/sh
      - -c
      - sleep 10; touch /tmp/healthy; sleep 30000
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:    # 采用volume方式,环境变量方式:略
  - name: foo
    configMap:
      name: myconfigmap4

kubectl apply -f mypod2.yml

查看config内容
kubectl exec -it mypod sh # 进入container后,执行 cat /etc/foo/config1,显示xxx.

多数情况下,配置文件都是以文件形式提供.所以在创建ConfigMap时,通常采用 --from-file或 YAML方式.读取ConfigMap通常采用Volume方式.
一般都是以2,4两种方式,具体使用方式和语法内容请参考P109-110.

思考: ConfigMap遇到很长的配置参数的时候,如何处理呢?Pod里需要创建很多个文件吗?
解决方案

apiVersion: v1 
data: 
  game-special-key: |     # game-special-key变成文件名
    enemies=aliens     # 这个是game-special-key文件的第一行
    lives=3     # 第二行
...

十三 Helm - K8S的包管理器

类似Centos的yum.

Helm架构
helm包括chart和release.
helm包含2个组件,Helm客户端和Tiller服务器.

Helm客户端安装
安装
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get |bash
查看
which helm
helm version # 因服务器端还没安装,这里会报无法连接
添加命令补全
helm completion bash > .helmrc
echo "source .helmrc" >> .bashrc

Tiller服务器端安装
helm init
查看
kubectl get --namespace=kube-system service tiller-deploy
kubectl get --namespace=kube-system deployments. tiller-deploy
kubectl get --namespace=kube-system pods tiller-deploy-6d6cc8dcb5-wvvr4
helm version # 能够看到服务器版本信息

Helm使用
搜索 helm search

执行命名添加权限
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

安装chart的mysql应用
helm install stable/mysql
会自动部署 Service,Deployment,Secret 和 PersistentVolumeClaim,并给与很多提示信息,比如mysql密码获取,连接端口等.

查看release各个对象
kubectl get service doltish-beetle-mysql
kubectl get deployments. doltish-beetle-mysql
kubectl get pods doltish-beetle-mysql-75fbddbd9d-f64j4
kubectl get pvc doltish-beetle-mysql
helm list # 显示已经部署的release

删除
helm delete doltish-beetle
kubectl get pods
kubectl get service
kubectl get deployments.
kubectl get pvc

chart详解

十四 K8S Dashboard

介绍K8S Dashboard的安装和使用方法.
Dashboard能完成日常管理的大部分工作,可以作为命令行工具kubectl的有益补充.同时,可以比较直观的看到各类资源情况.
Dashboard提供了kubectl的绝大部分功能.

部署
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml

查看

kubectl --namespace=kube-system get deployments. kubernetes-dashboard
kubectl --namespace=kube-system get service kubernetes-dashboard
kubectl --namespace=kube-system get pods -o wide

kubectl --namespace=kube-system get pods kubernetes-dashboard-57df4db6b-r6qn6 -o wide
NAME  READY STATUS  RESTARTS AGE   IP   NODE  NOMINATED NODE   READINESS GATES
kubernetes-dashboard-xxx 1/1 Running 0 40m 10.244.1.36 ip-10-0-0-106.xxx <none> <none>

修改service端口
修改成NodeIP:NodePort模式便于访问
kubectl --namespace=kube-system edit service kubernetes-dashboard
修改 type: ClusterIP 为 type: NodePort
确认修改成功
kubectl --namespace=kube-system get service kubernetes-dashboard

登陆Dashboard(需要token)
Dashboard支持Kubeconfig和Token两种认证方式,为简化配置,我们通过配置文件 dashboard-admin.yaml 为 Dashboard 默认用户赋予 admin 权限。
创建登陆token

mkdir dashboard; cd dashboard/
[centos@ip-10-0-0-105 dashboard]$ cat dashboard-admin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system

应用并获取token
kubectl apply -f dashboard-admin.yaml
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
把token填入web登陆里就可以了.

访问Dashboard
可以通过任何node节点IP访问到Dashboard(Dashboard的pod在node1上).如下
master https://3.1.57.171:32133/
node1 https://52.221.27.128:32133
node2 https://52.221.118.136:32133

宕机测试
将master1关机,再次访问node1,node2上面的服务,提示
node1上报:
Internal Server Error (500)
Get https://10.96.0.1:443/api/v1/namespaces/default/replicationcontrollers: dial tcp 10.96.0.1:443: i/o timeout
node2上报:
Internal Server Error (500)
Get https://10.96.0.1:443/api/v1/namespaces: dial tcp 10.96.0.1:443: connect: no route to host
将master开机(kubelet自启动),服务恢复正常.

将node1关机,,再次访问master,node2上面的服务
服务恢复大约5分钟(切换到node2上)
启动node1,大约需要1分钟,即可以通过node1IP:node1Port访问服务了(实际Pod仍在node2上)

Dashboard可以干什么
查看集群,节点等常规信息
可以在线修改yaml配置文件
查看describe信息
查看logs输出信息(kubectl logs)
查看pod日志
监控集群以及一些相关项(宕机会体现在Dashboard上)

十五 K8S 集群监控

Weave Scope 可以展示集群和应用的完整视图.其出色的交互性让用户能够轻松对容器化应用进行实时监控和问题诊断.
Heapster是K8S原生的集群监控方案.预定义的Dashboard能够从Cluster和Pods两个层次监控K8S.
Prometheus Operator可能是目前功能最全面的开源方案.除了能监控Node和Pod,还支持集群的各种管理组件,如API Server,Scheduler,Controller Manager等.

Weave Scope
部署
[centos@ip-10-0-0-105 ~]$ kubectl apply -f "https://cloud.weave.works/k8s/scope.yaml?k8s-version=$(kubectl version | base64 | tr -d '\n')"

修改端口
kubectl --namespace=weave edit service weave-scope-app
将ClusterIP更换为NodePort.

浏览器访问
可以通过hosts里的命令行,直接获取root权限.这个怎么考虑的呢?控制台登陆没有密码认证的啊....

删除
[centos@ip-10-0-0-105 ~]$ kubectl delete -f "https://cloud.weave.works/k8s/scope.yaml?k8s-version=$(kubectl version | base64 | tr -d '\n')"

Heapster
Heapster是K8S原生的集群监控方案.

部署
git clone https://github.com/kubernetes/heapster.git
kubectl apply -f heapster/deploy/kube-config/influxdb/
kubectl apply -f heapster/deploy/kube-config/rbac/heapster-rbac.yaml

查看
kubectl get --namespace=kube-system deployments. |grep -e heapster -e monitor
kubectl get --namespace=kube-system service|grep -e heapster -e monitor
kubectl get --namespace=kube-system pods -o wide|grep -e heapster -e monitor

修改端口
kubectl --namespace=kube-system edit service monitoring-grafana # 为type: NodePort
确认修改成功
kubectl get --namespace=kube-system service|grep -e heapster -e monitor

貌似看不到数据呢,需要登陆么?

Prometheus Operator
监控到的会比Weave Scope 和Heapster更多.
Prometheus Operator是CoreOS开发的.是目前功能最全面的开源方案.
Prometheus提供了数据搜集,存储,处理,可视化和告警一套完整的解决方案.
具体请参考在线文档或相关资料.

十六 K8S集群日志管理

K8S开发了一个Elasticsearch附加组件来实现集群的日志管理.
是Elasticsearch,Fluentd和Kibana的组合.
Elasticsearch是一个搜索引擎,负责存储日志并提供查询接口.
Fluentd负责从K8S搜集日志并发送给Elasticsearch.
Kibana提供Web GUI.
具体: 略.

十七 k8s kerbernetes 高可用方案部署

2019/03/18 Chenxin
https://kubernetes.io/zh/docs/admin/high-availability/
https://kubernetes.io/docs/setup/independent/high-availability/ 英文最新版
https://blog.51cto.com/bigboss/2174899 网友给出的1.11版本的方案,过于复杂
当前来看,k8s高可用自建方案还不够成熟.方式较多,也都比较复杂,官方目前还是内测阶段.建议待成熟后再跟进.当前可以研究云厂商的现成产品,如aws的EKS等.

2019/10/16 chenxin
https://juejin.im/post/5da44a765188251b643eb5e9 使用 Sealos 在 3 分钟内快速部署一个生产级别的 Kubernetes 高可用集群.
目前好像使用的还很少,但貌似很方便

十八 镜像仓库实践

2019/03/28 Chenxin
参考:
https://kubernetes.io/zh/docs/concepts/containers/images/ 官方资料 - 镜像
http://www.cnblogs.com/justmine/p/8666907.html 详解docker实战之搭建私有镜像仓库 - kurbernetes
http://www.cnblogs.com/justmine/p/8678758.html k8s实战之从私有仓库拉取镜像 - kubernetes
建议采用阿里云或AWS 私有仓库.这里为自行搭建的私有仓库测试.

十九 思考问题

K8S如何能踢掉一台故障的node?
kubectl delete nodes ip-10-0-0-107

node重新加入集群?
参考本笔记中"节点加入集群"部分的"重新加入集群"内容.

在所有服务正常提供情况下,突然主机node2宕机(aws控制台手动terminal该node),如何处理?
等待几分钟后,等k8s将所有原node2上的pod转移到其他node.
node2当前状态为"NotReady".手动在master上删除node2即可.

手动重启master或者node有什么影响?
因为只有单台master,重启会影响服务.不过起来后,服务会自动恢复.
重启node几乎没有影响,service有负载均衡能力,待故障node起来后,上面的pod会继续提供服务(如果重启需要时间很久,比如超过10分钟,k8s会自动将pod转移到正常的node上).

k8s如何根据当前负载情况自动横向扩容或缩容?
Kubernetes HPA(Horizontal Pod Autoscaling)Pod水平自动伸缩.
以下暂未通过(安装了heapster,但可能还需要metrics-server)

以下是HPA(V1版本)
Deployment和svc配置

[centos@ip-10-0-0-105 hpa]$ cat php-apache-deploy-and-svc.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  replicas: 1
  selector:
    matchLabels:
      app: php-apache
  template:
    metadata:
      name: php-apache
      labels:
        app: php-apache
    spec:
      containers:
      - name: php-apache
        image: gcr.io/google_containers/hpa-example
        resources:
          requests:
            cpu: 200m
        ports:
        - containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
spec:
  ports:
  - port: 80
  selector:
    app: php-apache

hpa配置

[centos@ip-10-0-0-105 hpa]$ cat hpa-php-apche.yml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1beta1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

准备用于压测的busybox

[centos@ip-10-0-0-105 hpa]$ cat busybox-pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  containers:
  - name: busybox
    image: busybox
    command: ["sleep", "3600"]

HPA(v2)控制器
HPA(v1)只能基于cpu实现自动弹性伸缩,HPA(v2)控制器基支持基于核心指标CPU和内存资源及基于任意自定义指标资源占用状态实现规模的弹性伸缩,它从metrics-service中请求查看核心指标。
如:

]# cat hpav2-mynginx.yaml 
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
...

外部如何访问到K8S集群里的service?
可以通过nodeIP+nodePort
当前service的yml配置支持type的LoadBalancer,一些公有云会自动分配一个公网IP给它.

二十 项目k8s部署

2019/03/28 Chenxin
三种模式选择
单master集群(自建集群)
阿里云k8s集群
亚马逊k8s集群

容器化改造需要处理问题
创建mysql,consul,redis服务 完成
创建私有docker资源仓库 完成
挂载外部存储到每个Pod上.将配置文件,脚本文件全部放此.日志也放在这里. 完成
控制台以及日志输出(写到Dockerfile或yml里).仍然重定向输出到文件中,且保持java前台运行(bash ls -al 2&>1 | tee ./ha.log),进程挂了后,文件不删除.暂时不用,因为故障自动转移功能. 完成
k8s的kubectl能否在普通账号下执行指令(可以)按照官方入门文档里的为EKS配置kubectl操作即可.其中会提示aws configure的执行(配置密钥). 完成
将PV改成S3测试,不方便修改文件.暂时不用这种方式 完成
分布式的fight,match如何处理?不使用容器(否则每个地区建集群,太费机器). 完成
停止nfs机器,会发生什么? worker机器上df -h 无法执行(卡在获取不到磁盘信息).登陆容器也不行.但容器状态还是run.等nfs机器手动开机后,一切恢复正常. 完成
创建主机,磁盘,内存监控报警.使用云平台自带功能实现. 略 完成
排查java进程异常或故障的时候,直接到云盘对应文件夹里去找报错信息(包括控制台,日志信息) 完成
使用xbzj账号管理docker.(将xbzj加入docker用户组) 完成
更换小的镜像测试(官网).openjdk:13-alpine跟java:latest还是有区别.比如没有/bin/bash文件,一些JVM参数不支持,载入/etc/profile后,会出现找不到java命令问题. 完成
测试java其他image,如 java:8-jre-alpine (jdk是开发包,含运行环境;jre只有运行环境,自动载入环境变量).FROM java:openjdk-8u111-jre基础镜像可以. 完成

以下因为 game.properties 配置文件里,很多需要自己的固定一个IP,故没有测试以下项.
多个gateway如何处理呢?gateway访问gamecenter: 不同service之间通过service的IP+Port去调用.
相同Pod多个实例如何处理控制台输出和日志文件.拷贝模板配置文件,gateway+时间+PodIP创建临时文件夹.启动进程前,读取此文件夹配置.container获取Pod IP的方法 https://blog.csdn.net/kozazyh/article/details/79463688 . 然后定期手动或自动到云盘上清理老旧文件夹(模板文件夹例外).
如何确保用户每次连上来的都是同一个gateway的Pod? 使用service的SessionAffinity负载均衡策略
容器故障后,重启后,如何获知?之前通过进程判断,自动拉起进程方式处理,并发送通知报警.可以查看pod的age时间.
镜像文件太大,上传到预热服比较慢,发布到生产服也慢,如何优化.

搭建gamecenter服务
说明
kubectl机器IP: 10.0.0.100
NFS机器IP: 10.0.0.101
EKS的worker node IP: 192.168.0.0/16
kubectl在系统的xbzj账号下执行各类管理指令.

搭建mysql,consul,redis服务
将gamecenter相关的class文件,启动脚本,配置文件,全部放置到/opt/java_demo/formal/[conf-dir Dockerfile manage-script package]
为节省资源,直接将服务搭建在 10.0.0.100 上.

创建Dockerfile文件.并将镜像上传到ECR里,供k8s调用
[xbzj@ip-10-0-0-100 gamecenter-df]$ cat Dockerfile

FROM java:openjdk-8u111-jre    # 这个镜像正常
#FROM java:latest    # 这个镜像正常
#FROM openjdk:12.0.1-jdk    # 启动应用的时候报错
#FROM java:8-jre-alpine    # 启动应用的时候报错
#FROM openjdk:13-alpine    # 启动应用的时候报错

#将当前目录复制到
COPY . /home/xbzj

#建立工作目录
WORKDIR /home/xbzj/manage

#运行命令
#RUN ./Manage.sh

#自启动命令
#CMD ["java","Main"]
ENTRYPOINT ["./Manage.sh"]
[xbzj@ip-10-0-0-100 gamecenter-df]$ cat .dockerignore    # 不打入镜像的文件
Dockerfile
./bak
.dockerignore

如下(在ERS里,创建1个xbzj库.登陆ERS/TAG/PUSH等,在aws控制台里有对应的提示指令,直接复制执行就可以了)(此步骤也在docker笔记中有对应说明)
docker build -t xbzj . # 这里的"."就是context(镜像构建上下文) -t指明创建的镜像名称
aws ecr get-login --no-include-email --region ap-southeast-1 # 登陆ECR,执行此命令的输出内容即完成登陆.
docker tag xbzj:latest 615624949551.dkr.ecr.ap-southeast-1.amazonaws.com/xbzj:latest
docker push 615624949551.dkr.ecr.ap-southeast-1.amazonaws.com/xbzj:latest # 上传到ECR仓库
注意,日常上传镜像只需要build,tag,push这3步就可以了.

启动容器测试
为了确保镜像是正确的,可以先随便找台docker测试一下是否正常.
docker container run -it my-java:first /bin/bash
ctrl + p,q 放置到后台
查看当前控制台输出 docker container attach container_id
登陆容器中的shell执行其他指令 docker container exec -it container_id /bin/bash ; # 可以确认需要的文件是否全部拷贝过来了,启动的端口是否如预期等.

创建K8S集群
参考aws k8s 集群入门部分.(使用cloudformation创建VPC;创建EKS;使用cloudformation创建worker组;配置kubectl;将worker加入集群;)

创建对等连接,修改路由,安全组
因为kubectl主机(上面创建了redis,mysql,consul等服务)在VPC-01(10.0.0.0/16)上.而eks的worker都在eks-vpc里.所以默认两个vpc里的主机是无法通讯的.
也就是worker上面启动的pod(image里打的redis地址是10.0.0.100)无法访问到redis服务.
需要创建vpc对等连接.
然后在两边的vpc上创建对应的路由表(让两个vpc能够找到对方).
然后分别修改kubectl主机,以及worker主机对应的安全组,允许彼此内网IP访问自己的所有服务.
如果需要kubectl主机访问worker或者worker上的pod,则记得在worker的安全组里加入对应的安全组规则(允许内网地址访问自己所有服务).默认生成worker的安全组的时候,并不会自动添加这一条安全策略,需要手动添加.

创建NFS服务器
参考对应NFS笔记内容.
附录,Manage.sh文件放置在 /data/gamecenter/

[root@ip-10-0-0-101 gamecenter]# cat Manage.sh
#!/bin/bash
#Function declaration:
#This script is used to start and stop the program
#Date:20180717 Update:
if [ -f /etc/init.d/functions ];then
. /etc/init.d/functions;
fi
source /etc/profile # 这里不能用于openjdk
export CODE_BASE="../package"
export GAME_ID='game002'
export SERVER_ID='aws-pub'
export REGION='default'
java $(exec ./BuildOpts.sh  --config_dir ./ --memory 500M)

创建gamecenter对应的PV,PVC

[root@ip-10-0-0-100 gamecenter]# cat gamecenter-pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: gamecenter-pv
spec:
  capacity:
    storage: 1Gi    # capacity指定PV的容量为1GB
  accessModes:
    - ReadWriteOnce # ReadWriteOnce,ReadOnlyMany,ReadWriteMany 3种.Once表示单个节点
  persistentVolumeReclaimPolicy: Retain    # PV的回收策略.Retain表示管理员手动回收.Recycle表示清除PV中的数据,同rm -rf /thevolume/*.Delete表示删除Storage Provider上对应的存储资源,例如AWS EBS.
  storageClassName: nfs # 指定PV的class为nfs.相当于为PV设置了一个分类,PVC可以指定class申请相应的class的PV.
  nfs:
    path: /data/gamecenter  # 指定PV在NFS服务器上对应的目录.这里直接将 BuildOpts.sh  Manage.sh 脚本以及配置文件game.properties  放到此文件夹.生成的一些日志也放在此.
    server: 10.0.0.101

[root@ip-10-0-0-100 gamecenter]# cat gamecenter-pvc.yml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gamecenter-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs

执行对应的PV,PVC:略.

制作主yml文件(含PV,PVC的说明).

[root@ip-10-0-0-100 gamecenter]# cat gamecenter.yml
apiVersion: apps/v1beta1 # apiVersion是当前配置格式的版本
kind: Deployment    # kind 是要创建的资源类型, 这里是Deployment.
metadata:   # metadata 是该资源的元数据, name 是必须的元数据项.
  name: gamecenter-deployment
spec:   # spec 部分是该Deployment的规格说明.
  replicas: 1   # replicas 指明副本数量, 默认是1.
  template: # template 定义Pod 的模板, 这是配置文件的重要部分.
    metadata:   # metadata 定义Pod的元数据, 至少要定义一个label. label 的key和value 可以任意指定.
      labels:
        app: gamecenter_server
    spec:   # spec 描述 Pod的规格,此部分定义Pod中的每一个容器的属性, name 和 image 是必须的.
      containers:
      - name: gamecenter
        image: 615624949551.dkr.ecr.ap-southeast-1.amazonaws.com/xbzj:latest
        #ports:
        #- containerPort: 80
        volumeMounts:
        - mountPath: "/home/xbzj/manage"
          name: gamecenterdata
      volumes:
        - name: gamecenterdata
          persistentVolumeClaim:
            claimName: gamecenter-pvc

---
apiVersion: v1 # apiVersion是当前配置格式的版本
kind: Service    # kind 是要创建的资源类型
metadata:   # metadata 是该资源的元数据, name 是必须的元数据项.
  name: gamecenter-svc   # Service 的名字
spec:   # spec 部分是该Deployment的规格说明.
  type: LoadBalancer # NodePort方式.还可以是ClusterIP,或者LoadBalancer
  loadBalancerSourceRanges: # 只允许内网访问(EKS会自动在负载均衡器里配置相应的安全组)
  - "10.0.0.0/16"
  - "192.168.0.0/16"
  selector:
    app: gamecenter_server # selector 指明挑选那些 label 为 run:httpd 的 Pod 作为 Service的后端
  ports:
  - protocol: TCP # 将service的8080端口映射到Pod的80端口,使用TCP协议
    name: name8200
    #nodePort: 30000 # 是各个节点上监听的端口(默认随机30000-32767)
    port: 8200  # 是ClusterIP上监听的端口(Service的)
    targetPort: 8200  # 是Pod监听的端口
  - protocol: TCP # 将service的8080端口映射到Pod的80端口,使用TCP协议
    name: name8201
    #nodePort: 30000 # 是各个节点上监听的端口(默认随机30000-32767)
    port: 8201  # 是ClusterIP上监听的端口(Service的)
    targetPort: 8201  # 是Pod监听的端口

备注: 只允许内网IP访问的yml配置参考 https://kubernetes.io/docs/concepts/services-networking/service/#network-load-balancer-support-on-aws-alpha

执行yml文件
kubectl apply -f gamecenter.yml
会依次创建pod,Deployment,service,以及对应的ELB,和ELB绑定的安全组.

posted @ 2019-10-24 12:00  ChanixChen  阅读(6098)  评论(1编辑  收藏  举报