kubernets 學習記錄

 

第1章 安裝

1.1 基礎配置

[root@k8s_master ~]# cat /etc/redhat-releasehtml

CentOS Linux release 7.5.1804 (Core)前端

[root@k8s_master ~]# systemctl status firewalldnode

● firewalld.service - firewalld - dynamic firewall daemonmysql

   Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)linux

   Active: inactive (dead)nginx

     Docs: man:firewalld(1)git

[root@k8s_master ~]# setenforce 0web

setenforce: SELinux is disabledredis

[root@k8s_master ~]# cat /etc/hostssql

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4

::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.138.150 k8s_master

192.168.138.151 k8s_client1

192.168.138.152  k8s_client2

配置阿里雲的yum源或系統默認的源;

[root@k8s_master ~]# yum -y install ntp docker

[root@k8s_master ~]# systemctl start ntpd

[root@k8s_master ~]# systemctl enable ntpd

Created symlink from /etc/systemd/system/multi-user.target.wants/ntpd.service to /usr/lib/systemd/system/ntpd.service.

Master節點操做

安裝etcd

[root@k8s_master ~]# yum -y install etcd

[root@k8s_master ~]# cat /etc/etcd/etcd.conf | grep -v "^#"

ETCD_DATA_DIR="/var/lib/etcd/default.etcd"

ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379,http://0.0.0.0:4001"

ETCD_NAME="master"

ETCD_ADVERTISE_CLIENT_URLS=http://k8s_master:2379,http://k8s_master:4001

[root@k8s_master ~]# systemctl enable etcd

[root@k8s_master ~]# systemctl start etcd

[root@k8s_master ~]# etcdctl -C http://k8s_master:2379 cluster-health

member 8e9e05c52164694d is healthy: got healthy result from http://k8s_master:2379

cluster is healthy

[root@k8s_master ~]# etcdctl -C http://k8s_master:4001 cluster-health

member 8e9e05c52164694d is healthy: got healthy result from http://k8s_master:2379

cluster is healthy

安裝docker(全部節點)

[root@k8s_master ~]# yum -y install docker

[root@k8s_master ~]# docker version

Client:

 Version:         1.13.1

 API version:     1.26

 Package version: docker-1.13.1-74.git6e3bb8e.el7.centos.x86_64

 Go version:      go1.9.4

 Git commit:      6e3bb8e/1.13.1

 Built:           Tue Aug 21 15:23:37 2018

 OS/Arch:         linux/amd64

 

Server:

 Version:         1.13.1

 API version:     1.26 (minimum version 1.12)

 Package version: docker-1.13.1-74.git6e3bb8e.el7.centos.x86_64

 Go version:      go1.9.4

 Git commit:      6e3bb8e/1.13.1

 Built:           Tue Aug 21 15:23:37 2018

 OS/Arch:         linux/amd64

 Experimental:    false

[root@k8s_master ~]# yum install kubernetes

[root@k8s_master ~]# cat /etc/kubernetes/apiserver | grep -Ev "^#|^$"

KUBE_API_ADDRESS="--insecure-bind-address=0.0.0.0"

KUBE_API_PORT="--port=8080"

KUBE_ETCD_SERVERS="--etcd-servers=http://192.168.138.150:2379"

KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.254.0.0/16"

KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,ResourceQuota"

KUBE_API_ARGS=""

[root@k8s_master ~]# cat /etc/kubernetes/config |grep -Ev "^#|^$"

KUBE_LOGTOSTDERR="--logtostderr=true"

KUBE_LOG_LEVEL="--v=0"

KUBE_ALLOW_PRIV="--allow-privileged=false"

KUBE_MASTER="--master=http://192.168.138.150:8080"

 

[root@k8s_master ~]# systemctl enable kube-apiserver kube-controller-manager kube-scheduler

[root@k8s_master ~]# systemctl start kube-apiserver kube-controller-manager kube-scheduler

[root@k8s_master ~]# netstat -lntup

Active Internet connections (only servers)

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name   

tcp        0      0 127.0.0.1:2380          0.0.0.0:*               LISTEN      936/etcd           

tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      639/rpcbind        

tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      932/sshd           

tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      1026/master        

tcp6       0      0 :::4001                 :::*                    LISTEN      936/etcd           

tcp6       0      0 :::6443                 :::*                    LISTEN      1047/kube-apiserver

tcp6       0      0 :::2379                 :::*                    LISTEN      936/etcd           

tcp6       0      0 :::10251                :::*                    LISTEN      651/kube-scheduler 

tcp6       0      0 :::10252                :::*                    LISTEN      634/kube-controller

tcp6       0      0 :::111                  :::*                    LISTEN      639/rpcbind        

tcp6       0      0 :::8080                 :::*                    LISTEN      1047/kube-apiserver        

tcp6       0      0 ::1:25                  :::*                    LISTEN      1026/master        

udp        0      0 0.0.0.0:803             0.0.0.0:*                           639/rpcbind        

udp        0      0 0.0.0.0:68              0.0.0.0:*                           2964/dhclient      

udp        0      0 192.168.138.150:8285    0.0.0.0:*                           1048/flanneld      

udp        0      0 0.0.0.0:111             0.0.0.0:*                           639/rpcbind        

udp        0      0 192.168.138.150:123     0.0.0.0:*                           2555/ntpd 

node節點安裝

[root@k8s_client1 ~]# yum install kubernetes

[root@k8s_client1 ~]#  cat /etc/kubernetes/config | egrep -v "^#|^$"

KUBE_LOGTOSTDERR="--logtostderr=true"

KUBE_LOG_LEVEL="--v=0"

KUBE_ALLOW_PRIV="--allow-privileged=false"

KUBE_MASTER="--master=http://192.168.138.150:8080"

[root@k8s_client1 ~]# cat /etc/kubernetes/kubelet | egrep -v "^#|^$"

KUBELET_ADDRESS="--address=0.0.0.0"

KUBELET_PORT="--port=10250"

KUBELET_HOSTNAME="--hostname-override=192.168.138.151"

KUBELET_API_SERVER="--api-servers=http://192.168.138.150:8080"

KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=registry.access.redhat.com/rhel7/pod-infrastructure:latest"

KUBELET_ARGS=""

[root@k8s_client1 ~]# systemctl enable kubelet kube-proxy

[root@k8s_client1 ~]# systemctl start kubelet kube-proxy   

[root@k8s_client1 ~]# netstat -lntup

Active Internet connections (only servers)

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name   

tcp        0      0 127.0.0.1:10248         0.0.0.0:*               LISTEN      1376/kubelet       

tcp        0      0 127.0.0.1:10249         0.0.0.0:*               LISTEN      925/kube-proxy     

tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      635/rpcbind        

tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      927/sshd           

tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      1014/master        

tcp6       0      0 :::4194                 :::*                    LISTEN      1376/kubelet       

tcp6       0      0 :::30372                :::*                    LISTEN      925/kube-proxy     

tcp6       0      0 :::10250                :::*                    LISTEN      1376/kubelet       

tcp6       0      0 :::9100                 :::*                    LISTEN      1947/node_exporter 

tcp6       0      0 :::10255                :::*                    LISTEN      1376/kubelet       

tcp6       0      0 :::111                  :::*                    LISTEN      635/rpcbind        

tcp6       0      0 :::30000                :::*                    LISTEN      925/kube-proxy     

tcp6       0      0 :::22                   :::*                    LISTEN      927/sshd           

tcp6       0      0 ::1:25                  :::*                    LISTEN      1014/master         

udp        0      0 0.0.0.0:807             0.0.0.0:*                           635/rpcbind        

udp        0      0 0.0.0.0:68              0.0.0.0:*                           744/dhclient       

udp        0      0 192.168.138.151:8285    0.0.0.0:*                           930/flanneld 

[root@k8s_master ~]# kubectl get nodes

NAME              STATUS    AGE

192.168.138.151   Ready     15d

192.168.138.152   Ready     15d

安裝flannel(全部節點)

yum install flannel

systemctl status firewalld

systemctl stop firewalld

systemctl disable firewalld

 

Master節點

[root@k8s_master ~]# cat /etc/sysconfig/flanneld | egrep -v "^#|^$"

FLANNEL_ETCD_ENDPOINTS="http://192.168.138.150:2379"

FLANNEL_ETCD_PREFIX="/atomic.io/network"

[root@k8s_master ~]# etcdctl mk //atomic.io/network/config '{"Network":"172.8.0.0/16"}'

[root@k8s_master ~]# systemctl enable flanneld

[root@k8s_master ~]# systemctl start flanneld  

[root@k8s_master ~]# for SERVICES in docker kube-apiserver kube-controller-manager kube-scheduler; do systemctl restart $SERVICES ; done

Node節點

[root@k8s_client1 ~]# cat /etc/sysconfig/flanneld | egrep -v "^#|^$"

FLANNEL_ETCD_ENDPOINTS="http://192.168.138.150:2379"

FLANNEL_ETCD_PREFIX="/atomic.io/network"

[root@k8s_client1 ~]# etcdctl mk //atomic.io/network/config '{"Network":"172.8.0.0/16"}'

[root@k8s_client1 ~]# systemctl enable flanneld

[root@k8s_client1 ~]# systemctl start flanneld 

[root@k8s_client1 ~]# systemctl restart kube-proxy kubelet docker     

[root@k8s_client1 ~]# ip a

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: eno16777728: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000

    link/ether 00:0c:29:f2:c5:36 brd ff:ff:ff:ff:ff:ff

    inet 192.168.138.151/24 brd 192.168.138.255 scope global noprefixroute dynamic eno16777728

       valid_lft 1126sec preferred_lft 1126sec

    inet6 fe80::20c:29ff:fef2:c536/64 scope link

       valid_lft forever preferred_lft forever

3: flannel0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1472 qdisc pfifo_fast state UNKNOWN group default qlen 500

    link/none

    inet 172.8.77.0/16 scope global flannel0

       valid_lft forever preferred_lft forever

    inet6 fe80::4ac1:c034:5096:3b04/64 scope link flags 800

       valid_lft forever preferred_lft forever

4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1472 qdisc noqueue state UP group default

    link/ether 02:42:27:e3:50:d7 brd ff:ff:ff:ff:ff:ff

    inet 172.8.77.1/24 scope global docker0

       valid_lft forever preferred_lft forever

    inet6 fe80::42:27ff:fee3:50d7/64 scope link

       valid_lft forever preferred_lft forever

全部節點

yum install *rhsm* -y

docker pull registry.access.redhat.com/rhel7/pod-infrastructure:latest  (k8s運行docker的基礎鏡像)

在阿里雲上拿下來的

部署dashboard

[root@k8s_master ~]# docker pull docker.io/siriuszg/kubernetes-dashboard-amd64:v1.5.1

[root@k8s_master ~]# cat kubernetes-dashboard.yaml

kind: Deployment

apiVersion: extensions/v1beta1

metadata:

  labels:

    app: kubernetes-dashboard

  name: kubernetes-dashboard

  namespace: kube-system

spec:

  replicas: 1

  revisionHistoryLimit: 10

  selector:

    matchLabels:

      app: kubernetes-dashboard

  template:

    metadata:

      labels:

        app: kubernetes-dashboard

# Comment the following annotation if Dashboard must not be deployed on master

      annotations:

        scheduler.alpha.kubernetes.io/tolerations: |

          [

            {

              "key": "dedicated",

              "operator": "Equal",

              "value": "master",

              "effect": "NoSchedule"

            }

          ]

    spec:

      containers:

      - name: kubernetes-dashboard

        image: docker.io/siriuszg/kubernetes-dashboard-amd64:v1.5.1

        imagePullPolicy: IfNotPresent

        ports:

        - containerPort: 9090

          protocol: TCP

        args:

 # Uncomment the following line to manually specify Kubernetes API server Host

 # If not specified, Dashboard will attempt to auto discover the API server and connect

 # to it. Uncomment only if the default does not work.

        - --apiserver-host=http://192.168.138.150:8080

        livenessProbe:

          httpGet:

            path: /

            port: 9090

          initialDelaySeconds: 30

          timeoutSeconds: 30

[root@k8s_master ~]# cat dashboard-service.yaml

kind: Service

apiVersion: v1

metadata:

  labels:

    app: kubernetes-dashboard

  name: kubernetes-dashboard

  namespace: kube-system

spec:

  type: NodePort

  ports:

  - port: 80

    targetPort: 9090

  selector:

app: kubernetes-dashboard

[root@k8s_master ~]# kubectl create -f kubernetes-dashboard.yaml
deployment "kubernetes-dashboard" created
[root@k8s_master ~]# kubectl create -f dashboard-service.yaml 
service "kubernetes-dashboard" created

[root@k8s_master ~]# kubectl get deployment --all-namespaces

NAMESPACE     NAME                   DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

default       httpd                  3         3         3            3           14d

default       nginx-deployment       3         3         3            3           14d

kube-system   kubernetes-dashboard   1         1         1            1           15d

kube-system   tiller-deploy          1         1         1            0           13d

weave         weave-scope-app        1         1         1            1           13d

[root@k8s_master ~]# kubectl get svc --all-namespaces

NAMESPACE     NAME                   CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE

default       httpd-svc              10.254.146.62    <nodes>       8082:30000/TCP   14d

default       kubernetes             10.254.0.1       <none>        443/TCP          15d

kube-system   kubernetes-dashboard   10.254.172.95    <nodes>       80:30372/TCP     15d

kube-system   tiller-deploy          10.254.112.169   <none>        44134/TCP        13d

weave         weave-scope-app        10.254.187.130   <none>        80/TCP           13d

訪問:192.168.138.150:8080/ui

 

 

 

 

參考文檔:http://blog.51cto.com/jonauil/2084986

http://www.javashuo.com/article/p-hierpwsa-bd.html

 

第2章 概念

Kubernetes 具有完善的集羣管理能力,包括多層次的安全防禦和准入機制、多租戶應用支撐能力、透明的服務註冊和服務發現機制、內建負載均衡器、故障發現和自我修復能力、服務滾動升級和在線擴容、可擴展的資源自動         調度機制、多粒度的資源配額管理能力。

Kubernetes 還提供完善的管理工具,涵蓋開發、部署測試、運維監控等各個環節。

Kubernetes主要由如下幾個核心組件組成:

etcd保存了整個集羣的狀態;

apiserver提供了資源操做的惟一入口,並提供認證、受權、訪問控制、API註冊和發現等機制;

controller manager負責維護集羣的狀態,好比故障檢測、自動擴展、滾動更新等;

scheduler負責資源的調度,按照預約的調度策略將Pod調度到相應的機器上;

kubelet負責維護容器的生命週期,同時也負責Volume(CVI)和網絡(CNI)的管理;

Container runtime負責鏡像管理以及Pod和容器的真正運行(CRI);

kube-proxy負責爲Service提供cluster內部的服務發現和負載均衡;

 

kube-dns負責爲整個集羣提供DNS服務

Ingress Controller爲服務提供外網入口

Heapster提供資源監控

Dashboard提供GUI

Federation提供跨可用區的集羣

Fluentd-elasticsearch提供集羣日誌採集、存儲與查詢

 

Kubernetes設計理念和功能其實就是一個相似Linux的分層架構,以下圖所示:

 

 

 

Cluster 
Cluster 是計算、存儲和網絡資源的集合,Kubernetes 利用這些資源運行各類基於容器的應用。

Master 
Master 是 Cluster 的大腦,它的主要職責是調度,即決定將應用放在哪裏運行。Master 運行 Linux 操做系統,能夠是物理機或者虛擬機。爲了實現高可用,能夠運行多個 Master。

Node 
Node 的職責是運行容器應用。Node 由 Master 管理,Node 負責監控並彙報容器的狀態,並根據 Master 的要求管理容器的生命週期。Node 運行在 Linux 操做系統,能夠是物理機或者是虛擬機。

Pod 
Pod 是 Kubernetes 的最小工做單元。每一個 Pod 包含一個或多個容器。Pod 中的容器會做爲一個總體被 Master 調度到一個 Node 上運行。

Kubernetes 引入 Pod 主要基於下面兩個目的:

可管理性。
有些容器天生就是須要緊密聯繫,一塊兒工做。Pod 提供了比容器更高層次的抽象,將它們封裝到一個部署單元中。Kubernetes 以 Pod 爲最小單位進行調度、擴展、共享資源、管理生命週期。

通訊和資源共享。
Pod 中的全部容器使用同一個網絡 namespace,即相同的 IP 地址和 Port 空間。它們能夠直接用 localhost 通訊。一樣的,這些容器能夠共享存儲,當 Kubernetes 掛載 volume 到 Pod,本質上是將 volume 掛載到 Pod 中的每個容器。

Pods 有兩種使用方式:

運行單一容器。
one-container-per-Pod 是 Kubernetes 最多見的模型,這種狀況下,只是將單個容器簡單封裝成 Pod。即使是隻有一個容器,Kubernetes 管理的也是 Pod 而不是直接管理容器。

運行多個容器。
但問題在於:哪些容器應該放到一個 Pod 中? 
答案是:這些容器聯繫必須 很是緊密,並且須要 直接共享資源。

Controller 
Kubernetes 一般不會直接建立 Pod,而是經過 Controller 來管理 Pod 的。Controller 中定義了 Pod 的部署特性,好比有幾個副本,在什麼樣的 Node 上運行等。爲了知足不一樣的業務場景,Kubernetes 提供了多種 Controller,包括 Deployment、ReplicaSet、DaemonSet、StatefuleSet、Job 等,咱們逐一討論。

Deployment 是最經常使用的 Controller,好比前面在線教程中就是經過建立 Deployment 來部署應用的。Deployment 能夠管理 Pod 的多個副本,並確保 Pod 按照指望的狀態運行。

ReplicaSet 實現了 Pod 的多副本管理。使用 Deployment 時會自動建立 ReplicaSet,也就是說 Deployment 是經過 ReplicaSet 來管理 Pod 的多個副本,咱們一般不須要直接使用 ReplicaSet。

DaemonSet 用於每一個 Node 最多隻運行一個 Pod 副本的場景。正如其名稱所揭示的,DaemonSet 一般用於運行 daemon。

StatefuleSet 可以保證 Pod 的每一個副本在整個生命週期中名稱是不變的。而其餘 Controller 不提供這個功能,當某個 Pod 發生故障須要刪除並從新啓動時,Pod 的名稱會發生變化。同時 StatefuleSet 會保證副本按照固定的順序啓動、更新或者刪除。

Job 用於運行結束就刪除的應用。而其餘 Controller 中的 Pod 一般是長期持續運行。

Service 
Deployment 能夠部署多個副本,每一個 Pod 都有本身的 IP,外界如何訪問這些副本呢?

經過 Pod 的 IP 嗎?
要知道 Pod 極可能會被頻繁地銷燬和重啓,它們的 IP 會發生變化,用 IP 來訪問不太現實。

答案是 Service。
Kubernetes Service 定義了外界訪問一組特定 Pod 的方式。Service 有本身的 IP 和端口,Service 爲 Pod 提供了負載均衡。

Namespace

若是有多個用戶或項目組使用同一個 Kubernetes Cluster,如何將他們建立的 Controller、Pod 等資源分開呢?

答案就是 Namespace。
Namespace 能夠將一個物理的 Cluster 邏輯上劃分紅多個虛擬 Cluster,每一個 Cluster 就是一個 Namespace。不一樣 Namespace 裏的資源是徹底隔離的。

Kubernetes 默認建立了兩個 Namespace。

default -- 建立資源時若是不指定,將被放到這個 Namespace 中。

kube-system -- Kubernetes 本身建立的系統資源將放到這個 Namespace 中

第3章 k8s架構

Master是Kubernetes Cluster的大腦,運行着以下Daemon服務:kube-apiserver、kube-scheduler、kube-controller-manager、etcd和Pod網絡(例如 flannel)。

API Server(kube-apiserver)

API Server提供HTTP/HTTPS RESTful API,即Kubernetes API。API Server是Kubernetes Cluster的前端接口,各類客戶端工具(CLI 或 UI)以及 Kubernetes其餘組件能夠經過它管理 Cluster 的各類資源。

 

Scheduler(kube-scheduler)

Scheduler負責決定將Pod 放在哪一個 Node上運行。Scheduler在調度時會充分考慮Cluster 的拓撲結構,當前各個節點的負載,以及應用對高可用、性能、數據親和性的需求。

 

Controller Manager(kube-controller-manager)

Controller Manager 負責管理 Cluster 各類資源,保證資源處於預期的狀態。Controller Manager由多種 controller組成,包括 replication controller、endpoints controller、namespace controller、serviceaccounts controller 等。

不一樣的 controller 管理不一樣的資源。例如 replication controller 管理 Deployment、StatefulSet、DaemonSet 的生命週期,namespace controller 管理 Namespace 資源。

 

etcd

etcd 負責保存 Kubernetes Cluster 的配置信息和各類資源的狀態信息。當數據發生變化時,etcd 會快速地通知 Kubernetes 相關組件。

 

Pod 網絡

Pod 要可以相互通訊,Kubernetes Cluster 必須部署 Pod 網絡,flannel 是其中一個可選方案。

 

Node是Pod運行的地方,Kubernetes支持 Docker、rkt 等容器 Runtime。Node上運行的 Kubernetes 組件有kubelet、kube-proxy 和Pod網絡(例如 flannel)。

kubelet

kubelet 是 Node 的 agent,當 Scheduler 肯定在某個 Node 上運行 Pod 後,會將 Pod 的具體配置信息(image、volume 等)發送給該節點的 kubelet,kubelet 根據這些信息建立和運行容器,並向 Master 報告運行狀態。

kube-proxy

service 在邏輯上表明瞭後端的多個 Pod,外界經過 service 訪問 Pod。service接收到的請求是如何轉發到 Pod 的呢?這就是 kube-proxy 要完成的工做。

每一個 Node 都會運行 kube-proxy 服務,它負責將訪問 service 的 TCP/UPD 數據流轉發到後端的容器。若是有多個副本,kube-proxy 會實現負載均衡。

Pod 網絡

Pod 要可以相互通訊,Kubernetes Cluster必須部署Pod網絡,flannel是其中一個可選方案。

[root@k8s_master ~]# kubectl get pod --all-namespaces -o wide

NAMESPACE     NAME                                    READY     STATUS    RESTARTS   AGE       IP           NODE

default       redis                                   1/1       Running   3          1h        172.8.95.2   192.168.138.130

kube-system   kubernetes-dashboard-2318246707-6vmhg   1/1       Running   4          1h        172.8.57.2   192.168.138.162

提早pull好鏡像;

[root@k8s_master ~]# kubectl run httpd-app --image=httpd --replicas=2

deployment "httpd-app" created

[root@k8s_master ~]# kubectl get deployment

NAME        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

httpd-app   2         2         2            2           16s

[root@k8s_master ~]# kubectl get pod -o wide

NAME                         READY     STATUS    RESTARTS   AGE       IP           NODE

httpd-app-3361905436-4p8vk   1/1       Running   0          30s       172.8.18.2   192.168.138.161

httpd-app-3361905436-cq6xd   1/1       Running   0          30s       172.8.95.3   192.168.138.130

redis                        1/1       Running   3          1h        172.8.95.2   192.168.138.130

 

kubectl 發送部署請求到 API Server。

API Server 通知 Controller Manager 建立一個 deployment 資源。

Scheduler 執行調度任務,將兩個副本 Pod 分發到 k8s-node1 和 k8s-node2。

k8s-node1 和 k8s-node2 上的 kubelet 在各自的節點上建立並運行 Pod。

補充兩點:

應用的配置和當前狀態信息保存在etcd 中,執行kubectl get pod 時,API Server 會從 etcd 中讀取這些數據。

flannel會爲每一個Pod都分配IP。由於沒有建立service,目前kube-proxy 還沒參與進來。

第4章 運行應用

前面咱們已經瞭解到,Kubernetes經過各類Controller來管理Pod的生命週期。爲了知足不一樣業務場景,Kubernetes開發了Deployment、ReplicaSet、DaemonSet、StatefuleSet、Job 等多種Controller。

4.1 Deployment

[root@k8s_master ~]# kubectl get deployment httpd-app

NAME        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

httpd-app   2         2         2            2           24m

[root@k8s_master ~]# kubectl describe deployment httpd-app

Name:           httpd-app

Namespace:      default

CreationTimestamp:  Thu, 30 Aug 2018 19:57:36 +0800

Labels:         run=httpd-app

Selector:       run=httpd-app

Replicas:       2 updated | 2 total | 2 available | 0 unavailable

StrategyType:      RollingUpdate

MinReadySeconds:    0

RollingUpdateStrategy:  1 max unavailable, 1 max surge

Conditions:

  Type      Status  Reason

  ----      ------  ------

  Available     True    MinimumReplicasAvailable

OldReplicaSets: <none>

NewReplicaSet:  httpd-app-3361905436 (2/2 replicas created)

Events:

  FirstSeen LastSeen    Count   From               SubObjectPath   Type        Reason          Message

  --------- --------    -----   ----               -------------   --------    ------          -------

  25m       25m     1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled up replica set httpd-app-3361905436 to 2

大部份內容都是自解釋的,咱們重點看最下面部分。這裏告訴咱們建立了一個 ReplicaSet nginx-deployment-1260880958,Events 是 Deployment 的日誌,記錄了ReplicaSet 的啓動過程。

經過上面的分析,也驗證了Deployment經過ReplicaSet來管理Pod的事實接着咱們將注意力切換到 nginx-deployment-1260880958,執行 kubectl describe replicaset:

 

Controlled By指明此ReplicaSet是由 Deployment nginx-deployment建立。Events記錄了兩個副本Pod的建立。接着咱們來看Pod執行 kubectl get pod:

[root@k8s_master ~]# kubectl get pod

NAME                         READY     STATUS    RESTARTS   AGE

httpd-app-3361905436-4p8vk   1/1       Running   0          2h

httpd-app-3361905436-cq6xd   1/1       Running   0          2h

redis                        1/1       Running   3          3h

 

 

Controlled By 指明此Pod是由ReplicaSet nginx-deployment-1260880958 建立。Events記錄了Pod的啓動過程。若是操做失敗(好比 image 不存在)也能在這裏查看到緣由。

總結一下這個過程:

用戶經過 kubectl 建立 Deployment。

Deployment 建立 ReplicaSet。

ReplicaSet 建立 Pod。

 

從上圖也能夠看出,對象的命名方式是:子對象的名字=父對象名字 + 隨機字符串或數字。

4.2 K8s資源建立方式

Kubernetes 支持兩種方式建立資源:

1.用 kubectl 命令直接建立,好比:

kubectl run nginx-deployment --image=nginx:1.7.9 --replicas=2

在命令行中經過參數指定資源的屬性。

2. 經過配置文件和 kubectl apply 建立,要完成前面一樣的工做,可執行命令:

kubectl apply -f nginx.yml

nginx.yml

 

 

資源的屬性寫在配置文件中,文件格式爲 YAML。

下面對這兩種方式進行比較。

基於命令的方式:

簡單直觀快捷,上手快。

適合臨時測試或實驗。

 

基於配置文件的方式:

配置文件描述了 What,即應用最終要達到的狀態。

配置文件提供了建立資源的模板,可以重複部署。

能夠像管理代碼同樣管理部署。

適合正式的、跨環境的、規模化部署。

這種方式要求熟悉配置文件的語法,有必定難度。

後面咱們都將採用配置文件的方式,你們須要儘快熟悉和掌握。

 

kubectl apply 不但可以建立 Kubernetes 資源,也能對資源進行更新,很是方便。不過 Kubernets 還提供了幾個相似的命令,例如 kubectl create、kubectl replace、kubectl edit 和 kubectl patch。

爲避免形成沒必要要的困擾,咱們會盡可能只使用 kubectl apply

[root@k8s_master ~]# kubectl apply -f nginx.yml

deployment "nginx-deployment" created

[root@k8s_master ~]# cat nginx.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: nginx-deployment

spec:

  replicas: 2

  template:

    metadata:

      labels:

        app: web_server

    spec:

      containers:

      - name: nginx

        image: nginx:1.7.9

[root@k8s_master ~]#  kubectl get pod --all-namespaces

NAMESPACE     NAME                                    READY     STATUS    RESTARTS   AGE

default       httpd-app-3361905436-4p8vk              1/1       Running   0          2h

default       httpd-app-3361905436-cq6xd              1/1       Running   0          2h

default       nginx-deployment-2472058307-1lrwc       1/1       Running   0          56s

default       nginx-deployment-2472058307-n786m       1/1       Running   0          56s

default       redis                                   1/1       Running   3          4h

kube-system   kubernetes-dashboard-2318246707-6vmhg   1/1       Running   4          4h

 

yaml配置文件解釋

 

apiVersion 是當前配置格式的版本。

kind 是要建立的資源類型,這裏是 Deployment。

metadata 是該資源的元數據,name 是必需的元數據項。

spec 部分是該 Deployment 的規格說明。

replicas 指明副本數量,默認爲 1。

template 定義 Pod 的模板,這是配置文件的重要部分。

metadata 定義 Pod 的元數據,至少要定義一個 label。label 的 key 和 value 能夠任意指定。

spec 描述 Pod 的規格,此部分定義 Pod 中每個容器的屬性,name 和 image 是必需的。

此nginx.yml是一個最簡單的Deployment配置文件,後面咱們學習Kubernetes各項功能時會逐步豐富這個文件。

執行 kubectl apply -f nginx.yml:

[root@k8s_master ~]# kubectl get deployment

NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

httpd-app          2         2         2            2           2h

nginx-deployment   2         2         2            2           7m

[root@k8s_master ~]# kubectl get replicaset

NAME                          DESIRED   CURRENT   READY     AGE

httpd-app-3361905436          2         2         2         2h

nginx-deployment-2472058307   2         2         2         7m

[root@k8s_master ~]# kubectl get pod -o wide

NAME                                READY     STATUS    RESTARTS   AGE       IP           NODE

httpd-app-3361905436-4p8vk          1/1       Running   0          2h        172.8.18.2   192.168.138.161

httpd-app-3361905436-cq6xd          1/1       Running   0          2h        172.8.95.3   192.168.138.130

nginx-deployment-2472058307-1lrwc   1/1       Running   0          7m        172.8.57.3   192.168.138.162

nginx-deployment-2472058307-n786m   1/1       Running   0          7m        172.8.18.3   192.168.138.161

redis                               1/1       Running   3          4h        172.8.95.2   192.168.138.130

Deployment、ReplicaSet、Pod 都已經就緒。若是要刪除這些資源,執行kubectl delete deployment nginx-deployment或者kubectl delete -f nginx.yml。

 

 

4.3 Scale Up/Down

伸縮(Scale Up/Down)是指在線增長或減小 Pod 的副本數。

Deployment nginx-deployment 初始是兩個副本

修改nginx.yml

[root@k8s_master ~]# cat nginx.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: nginx-deployment

spec:

  replicas: 5

  template:

    metadata:

      labels:

        app: web_server

    spec:

      containers:

      - name: nginx

        image: nginx:1.7.9

[root@k8s_master ~]# kubectl apply -f nginx.yml

deployment "nginx-deployment" configured

[root@k8s_master ~]# kubectl get pod

NAME                                READY     STATUS    RESTARTS   AGE

nginx-deployment-2472058307-1lrwc   1/1       Running   0          19m

nginx-deployment-2472058307-6mj1s   1/1       Running   0          20s

nginx-deployment-2472058307-b5bp7   1/1       Running   0          20s

nginx-deployment-2472058307-n786m   1/1       Running   0          19m

nginx-deployment-2472058307-sthxc   1/1       Running   0          20s

redis                               1/1       Running   3          4h

[root@k8s_master ~]# cat nginx.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: nginx-deployment

spec:

  replicas: 3

  template:

    metadata:

      labels:

        app: web_server

    spec:

      containers:

      - name: nginx

        image: nginx:1.7.9

[root@k8s_master ~]# kubectl apply -f nginx.yml

deployment "nginx-deployment" configured

[root@k8s_master ~]# kubectl get pod

NAME                                READY     STATUS    RESTARTS   AGE

nginx-deployment-2472058307-1lrwc   1/1       Running   0          21m

nginx-deployment-2472058307-b5bp7   1/1       Running   0          2m

nginx-deployment-2472058307-n786m   1/1       Running   0          21m

redis                               1/1       Running   3          4h

4.4 Failover故障遷移

關閉一個節點

等待一段時間,Kubernetes會檢查到k8s-node2不可用,將k8s-node2上的Pod標記爲Unknown狀態,並在 k8s-node1上新建立兩個 Pod,維持總副本數爲 3

當 k8s-node2 恢復後,Unknown 的 Pod 會被刪除,不過已經運行的 Pod 不會從新調度回 k8s-node2。

4.5 用label控制Pod的位置

[root@k8s_master ~]# kubectl get nodes

NAME              STATUS    AGE

192.168.138.130   Ready     5h

192.168.138.161   Ready     5h

192.168.138.162   Ready     5h

[root@k8s_master ~]# kubectl get node --show-labels

NAME              STATUS    AGE       LABELS

192.168.138.130   Ready     5h        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.138.130

192.168.138.161   Ready     5h        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.138.161

192.168.138.162   Ready     5h        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.138.162

[root@k8s_master ~]# kubectl label node 192.168.138.162 disktype=ssd

node "192.168.138.162" labeled

[root@k8s_master ~]# kubectl get node --show-labels

NAME              STATUS    AGE       LABELS

192.168.138.130   Ready     5h        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.138.130

192.168.138.161   Ready     5h        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.138.161

192.168.138.162   Ready     5h        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,kubernetes.io/hostname=192.168.138.162

修改nginx.yml

[root@k8s_master ~]# cat nginx.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: nginx-deployment

spec:

  replicas: 3

  template:

    metadata:

      labels:

        app: web_server

    spec:

      containers:

      - name: nginx

        image: nginx:1.7.9

      nodeSelector:

        disktype: ssd

[root@k8s_master ~]# kubectl delete -f nginx.yml

deployment "nginx-deployment" deleted

[root@k8s_master ~]# kubectl get pod

NAME      READY     STATUS    RESTARTS   AGE

redis     1/1       Running   3          4h

[root@k8s_master ~]# vim nginx.yml

[root@k8s_master ~]# kubectl get pod -o wide

NAME      READY     STATUS    RESTARTS   AGE       IP           NODE

redis     1/1       Running   3          4h        172.8.95.2   192.168.138.130

[root@k8s_master ~]# kubectl apply -f nginx.yml

deployment "nginx-deployment" created

[root@k8s_master ~]# kubectl get pod -o wide

NAME                                READY     STATUS    RESTARTS   AGE       IP           NODE

nginx-deployment-1851826925-f0gpc   1/1       Running   0          10s       172.8.57.4   192.168.138.162

nginx-deployment-1851826925-phw8g   1/1       Running   0          10s       172.8.57.5   192.168.138.162

nginx-deployment-1851826925-ttsqm   1/1       Running   0          10s       172.8.57.3   192.168.138.162

redis                               1/1       Running   3          4h        172.8.95.2   192.168.138.130

要刪除 label disktype,執行以下命令:

[root@k8s_master ~]# kubectl label node 192.168.138.162 disktype-

node "192.168.138.162" labeled

[root@k8s_master ~]# kubectl get node --show-labels

NAME              STATUS    AGE       LABELS

192.168.138.130   Ready     6h        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.138.130

192.168.138.161   Ready     6h        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.138.161

192.168.138.162   Ready     6h        beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.138.162

不過此時 Pod 並不會從新部署,依然在 192.168.138.162上運行

4.6 DaemonSet典型應用

由於部署緣由,待補充;

Deployment 部署的副本 Pod 會分佈在各個 Node 上,每一個 Node 均可能運行好幾個副本。DaemonSet 的不一樣之處在於:每一個 Node 上最多隻能運行一個副本。

DaemonSet 的典型應用場景有:

在集羣的每一個節點上運行存儲 Daemon,好比 glusterd 或 ceph。

在每一個節點上運行日誌收集 Daemon,好比 flunentd 或 logstash。

在每一個節點上運行監控 Daemon,好比 Prometheus Node Exporter 或 collectd。

其實 Kubernetes 本身就在用 DaemonSet 運行系統組件。執行以下命令:

[root@k8s_master ~]# kubectl get daemonset --namespace=kube-system

No resources found.

Kubernetes集羣中每一個當前運行的資源均可以經過kubectl edit查看其配置和運行狀態,好比 kubectl edit deployment nginx-deployment。

[root@k8s_master ~]# kubectl edit deployment nginx-deployment

 

# Please edit the object below. Lines beginning with a '#' will be ignored,

# and an empty file will abort the edit. If an error occurs while saving this file will be

# reopened with the relevant failures.

#

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  annotations:

    deployment.kubernetes.io/revision: "1"

    kubectl.kubernetes.io/last-applied-configuration: '{"kind":"Deployment","apiVersion":"extensions/v1beta1","metadata":{"name":"nginx-deployment","creationTimestamp":null},"spec":{"replicas":3,"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"web_server"}},"spec":{"containers":[{"name":"nginx","image":"nginx:1.7.9","resources":{}}],"nodeSelector":{"disktype":"ssd"}}},"strategy":{}},"status":{}}'

  creationTimestamp: 2018-08-30T15:11:23Z

  generation: 1

  labels:

    app: web_server

  name: nginx-deployment

  namespace: default

  resourceVersion: "36897"

  selfLink: /apis/extensions/v1beta1/namespaces/default/deployments/nginx-deployment

  uid: f55b248b-ac66-11e8-97ec-000c29cc726b

spec:

  replicas: 3

  selector:

    matchLabels:

      app: web_server

  strategy:

    rollingUpdate:

      maxSurge: 1

      maxUnavailable: 1

    type: RollingUpdate

  template:

    metadata:

      creationTimestamp: null

      labels:

        app: web_server

    spec:

      containers:

      - image: nginx:1.7.9

        imagePullPolicy: IfNotPresent

        name: nginx

        resources: {}

        terminationMessagePath: /dev/termination-log

      dnsPolicy: ClusterFirst

      nodeSelector:

        disktype: ssd

      restartPolicy: Always

      securityContext: {}

      terminationGracePeriodSeconds: 30

status:

  availableReplicas: 3

  conditions:

  - lastTransitionTime: 2018-08-30T16:34:59Z

    lastUpdateTime: 2018-08-30T16:34:59Z

    message: Deployment has minimum availability.

    reason: MinimumReplicasAvailable

    status: "True"

    type: Available

  observedGeneration: 1

  replicas: 3

  updatedReplicas: 3

kind: deployment類型的資源。

containers 定義了nginx的容器。

status是當前deployment的運行時狀態,這個部分是 kubectl edit特有的。

[root@k8s_master ~]# cat prom.yml

apiVersion: extensions/v1beta1

kind: DaemonSet

metadata:

  name: node-exporter-daemonset

spec:

  template:

    metadata:

      labels:

        app: prometheus

    spec:

      hostNetwork: true   #直接使用 Host 的網絡。

      containers:

      - name: node-exporter

        image: prom/node-exporter

        imagePullPolicy: IfNotPresent

        command:  #設置容器啓動命令。

        - /bin/node_exporter

        - --path.procfs

        - /host/proc

        - --path.sysfs

        - /host/sys

        - --collector.filesystem.ignored-mount-points

        - ^/(sys|proc|dev|host|etc)($|/)

        volumeMounts: #經過 Volume 將 Host 路徑 /proc、/sys 和 / 映射到容器中。

        - name: proc

          mountPath: /host/proc

        - name: sys

          mountPath: /host/sys

        - name: root

          mountPath: /rootfs

      volumes:

      - name: proc

        hostPath:

          path: /proc

      - name: sys

        hostPath :

          path: /sys

      - name: root

        hostPath:

          path: /

[root@k8s_master ~]# kubectl apply -f prom.yml

daemonset "node-exporter-daemonset" created

[root@k8s_master ~]# kubectl get pod -o wide

NAME                                READY     STATUS    RESTARTS   AGE       IP                NODE

nginx-deployment-1851826925-f0gpc   1/1       Running   0          4h        172.8.57.4        192.168.138.162

nginx-deployment-1851826925-phw8g   1/1       Running   0          4h        172.8.57.5        192.168.138.162

nginx-deployment-1851826925-ttsqm   1/1       Running   0          4h        172.8.57.3        192.168.138.162

node-exporter-daemonset-0b3zt       1/1       Running   0          4m        192.168.138.130   192.168.138.130

node-exporter-daemonset-mr3qp       1/1       Running   0          4m        192.168.138.162   192.168.138.162

node-exporter-daemonset-xl4gb       1/1       Running   0          4m        192.168.138.161   192.168.138.161

redis                               1/1       Running   3          9h        172.8.95.2        192.168.138.130

4.7 用k8s運行一次性任務

容器按照持續運行的時間可分爲兩類:服務類容器和工做類容器。

服務類容器一般持續提供服務,須要一直運行,好比 http server,daemon 等。工做類容器則是一次性任務,好比批處理程序,完成後容器就退出。

Kubernetes的Deployment、ReplicaSet和DaemonSet都用於管理服務類容器;對於工做類容器,咱們用 Job。

4.7.1 一次性job

[root@k8s_master ~]# cat myjob.yml

apiVersion: batch/v1 #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 。

[root@k8s_master ~]# kubectl apply -f myjob.yml

job "myjob" created

[root@k8s_master ~]# kubectl get pod

NAME                                READY     STATUS    RESTARTS   AGE

nginx-deployment-1851826925-f0gpc   1/1       Running   0          7h

nginx-deployment-1851826925-phw8g   1/1       Running   0          7h

nginx-deployment-1851826925-ttsqm   1/1       Running   0          7h

node-exporter-daemonset-0b3zt       1/1       Running   0          2h

node-exporter-daemonset-mr3qp       1/1       Running   0          2h

node-exporter-daemonset-xl4gb       1/1       Running   0          2h

redis                               1/1       Running   3          11h

[root@k8s_master ~]# kubectl get job

NAME      DESIRED   SUCCESSFUL   AGE

myjob     1         1            57s

DESIRED和SUCCESSFUL都爲1,表示按照預期啓動了一個Pod,而且已經成功執行。kubectl get pod查看Pod的狀態

[root@k8s_master ~]# kubectl get pod --show-all

NAME                                READY     STATUS      RESTARTS   AGE

myjob-24bwp                         0/1       Completed   0          3m

nginx-deployment-1851826925-f0gpc   1/1       Running     0          7h

nginx-deployment-1851826925-phw8g   1/1       Running     0          7h

nginx-deployment-1851826925-ttsqm   1/1       Running     0          7h

node-exporter-daemonset-0b3zt       1/1       Running     0          2h

node-exporter-daemonset-mr3qp       1/1       Running     0          2h

node-exporter-daemonset-xl4gb       1/1       Running     0          2h

redis                               1/1       Running     3          11h

由於Pod執行完畢後容器已經退出,須要用--show-all才能查看Completed狀態的Pod。

[root@k8s_master ~]# kubectl logs myjob-24bwp

hello k8s job!

[root@k8s_master ~]# kubectl delete -f myjob.yml

job "myjob" deleted

修改myjob.yml,故意引入一個錯誤:

[root@k8s_master ~]# cat myjob.yml

apiVersion: batch/v1

kind: Job

metadata:

  name: myjob

spec:

  template:

    metadata:

      name: myjob

    spec:

      containers:

      - name: hello

        image: busybox

        command: ["invalidecho", "hello k8s job! "]

      restartPolicy: Never

[root@k8s_master ~]# kubectl get job

NAME      DESIRED   SUCCESSFUL   AGE

myjob     1         0            28s

[root@k8s_master ~]# kubectl get pod --show-all

NAME                                READY     STATUS               RESTARTS   AGE

myjob-0p538                         0/1       ContainerCreating    0          2s

myjob-76vb7                         0/1       ContainerCannotRun   0          14s

myjob-8blcd                         0/1       ContainerCannotRun   0          26s

myjob-8r379                         0/1       ContainerCannotRun   0          32s

myjob-hd55m                         0/1       ContainerCannotRun   0          39s

myjob-pwvk2                         0/1       ContainerCannotRun   0          20s

myjob-qznph                         0/1       ContainerCannotRun   0          8s

nginx-deployment-1851826925-f0gpc   1/1       Running              0          7h

nginx-deployment-1851826925-phw8g   1/1       Running              0          7h

nginx-deployment-1851826925-ttsqm   1/1       Running              0          7h

node-exporter-daemonset-0b3zt       1/1       Running              0          2h

node-exporter-daemonset-mr3qp       1/1       Running              0          2h

node-exporter-daemonset-xl4gb       1/1       Running              0          2h

redis                               1/1       Running              3          12h

緣由是:當第一個Pod啓動時,容器失敗退出,根據 restartPolicy: Never,此失敗容器不會被重啓,但Job DESIRED的Pod是1,目前SUCCESSFUL爲0,不知足,因此 Job controller會啓動新的 Pod,直到 SUCCESSFUL 爲 1。對於咱們這個例子,SUCCESSFUL 永遠也到不了1,因此 Job controller會一直建立新的Pod。爲了終止這個行爲,只能刪除Job。

[root@k8s_master ~]# kubectl delete -f myjob.yml

job "myjob" deleted

[root@k8s_master ~]# vim myjob.yml

[root@k8s_master ~]# cat myjob.yml

apiVersion: batch/v1

kind: Job

metadata:

  name: myjob

spec:

  template:

    metadata:

      name: myjob

    spec:

      containers:

      - name: hello

        image: busybox

        command: ["invalidecho", "hello k8s job! "]

      restartPolicy: OnFailure

 

[root@k8s_master ~]# kubectl get pod --show-all

NAME                                READY     STATUS             RESTARTS   AGE

myjob-sccpv                         0/1       CrashLoopBackOff   3          2m

nginx-deployment-1851826925-f0gpc   1/1       Running            0          7h

nginx-deployment-1851826925-phw8g   1/1       Running            0          7h

nginx-deployment-1851826925-ttsqm   1/1       Running            0          7h

node-exporter-daemonset-0b3zt       1/1       Running            0          3h

node-exporter-daemonset-mr3qp       1/1       Running            0          3h

node-exporter-daemonset-xl4gb       1/1       Running            0          3h

redis                               1/1       Running            3          12h

這裏只有一個Pod,不過RESTARTS爲3,並且不斷增長,說明OnFailure生效,容器失敗後會自動重啓。

[root@k8s_master ~]# kubectl delete job myjob

job "myjob" deleted

[root@k8s_master ~]# kubectl get job

No resources found.

4.7.2 並行job

有時,咱們但願能同時運行多個Pod,提升Job的執行效率。這個能夠經過parallelism設置。這裏咱們將並行的Pod數量設置爲2,實踐一下:

[root@k8s_master ~]# kubectl apply -f myjob.yml

job "myjob" created

[root@k8s_master ~]# cat myjob.yml

apiVersion: batch/v1

kind: Job

metadata:

  name: myjob

spec:

  parallelism: 2

  template:

    metadata:

      name: myjob

    spec:

      containers:

      - name: hello

        image: busybox

        command: ["echo", "hello k8s job! "]

      restartPolicy: OnFailure

[root@k8s_master ~]# kubectl get job

NAME      DESIRED   SUCCESSFUL   AGE

myjob     <none>    2            1m

[root@k8s_master ~]# kubectl get pod --show-all

NAME                                READY     STATUS      RESTARTS   AGE

myjob-0ll5n                         0/1       Completed   0          1m

myjob-r8fjz                         0/1       Completed   0          1m

nginx-deployment-1851826925-f0gpc   1/1       Running     0          7h

nginx-deployment-1851826925-phw8g   1/1       Running     0          7h

nginx-deployment-1851826925-ttsqm   1/1       Running     0          7h

node-exporter-daemonset-0b3zt       1/1       Running     0          3h

node-exporter-daemonset-mr3qp       1/1       Running     0          3h

node-exporter-daemonset-xl4gb       1/1       Running     0          3h

redis                               1/1       Running     3          12h

 

咱們還能夠經過completions設置Job成功完成Pod的總數

上面配置的含義是:每次運行兩個Pod,直到總共有6個Pod成功完成。實踐一下

[root@k8s_master ~]# cat myjob.yml

apiVersion: batch/v1

kind: Job

metadata:

  name: myjob

spec:

  completions: 6

  parallelism: 2

  template:

    metadata:

      name: myjob

    spec:

      containers:

      - name: hello

        image: busybox

        command: ["echo", "hello k8s job! "]

      restartPolicy: OnFailure

[root@k8s_master ~]# kubectl apply -f myjob.yml

The Job "myjob" is invalid: spec.completions: Invalid value: 6: field is immutable

[root@k8s_master ~]# kubectl delete job myjob

job "myjob" deleted

[root@k8s_master ~]# kubectl apply -f myjob.yml

job "myjob" created

[root@k8s_master ~]# kubectl get pod --show-all

NAME                                READY     STATUS      RESTARTS   AGE

myjob-0kll9                         0/1       Completed   0          1m

myjob-35hgj                         0/1       Completed   0          46s

myjob-cchqx                         0/1       Completed   0          58s

myjob-jbmc1                         0/1       Completed   0          48s

myjob-rjktd                         0/1       Completed   0          1m

myjob-tpd9b                         0/1       Completed   0          54s

nginx-deployment-1851826925-f0gpc   1/1       Running     0          7h

nginx-deployment-1851826925-phw8g   1/1       Running     0          7h

nginx-deployment-1851826925-ttsqm   1/1       Running     0          7h

node-exporter-daemonset-0b3zt       1/1       Running     0          3h

node-exporter-daemonset-mr3qp       1/1       Running     0          3h

node-exporter-daemonset-xl4gb       1/1       Running     0          3h

redis                               1/1       Running     3          12h

[root@k8s_master ~]# kubectl get job

NAME      DESIRED   SUCCESSFUL   AGE

myjob     6         6            1m

DESIRED和SUCCESSFUL均爲6,符合預期。若是不指定completions和parallelism,默認值均爲1。

4.7.3 定時job

Linux中有cron程序定時執行任務,Kubernetes的CronJob提供了相似的功能,能夠定時執行Job。

[root@k8s_master ~]# cat cronjob.yml

apiVersion: batch/v2alpha1   #batch/v2alpha1 是當前 CronJob的apiVersion

kind: CronJob  #指明當前資源的類型爲 CronJob

metadata:

  name: hello

spec:

  schedule: "*/1 * * * *"  #schedule 指定何時運行 Job,其格式與 Linux cron一致。

  jobTemplate:   #jobTemplate定義Job的模板,格式與前面Job一致。

    spec:

      template:

        spec:

          containers:

          - name: hello

            image: busybox

            command: ["echo", "hello k8s job! "]

          restartPolicy: OnFailure

[root@k8s_master ~]# kubectl apply -f cronjob.yml

error: error validating "cronjob.yml": error validating data: couldn't find type: v2alpha1.CronJob; if you choose to ignore these errors, turn validation off with --validate=false

[root@k8s_master ~]# kubectl api-versions

apps/v1beta1

authentication.k8s.io/v1beta1

authorization.k8s.io/v1beta1

autoscaling/v1

batch/v1

certificates.k8s.io/v1alpha1

extensions/v1beta1

policy/v1beta1

rbac.authorization.k8s.io/v1alpha1

storage.k8s.io/v1beta1

v1

沒有batch/v2alpha1

[root@k8s_master ~]# kubectl version

Client Version: version.Info{Major:"1", Minor:"5", GitVersion:"v1.5.2", GitCommit:"269f928217957e7126dc87e6adfa82242bfe5b1e", GitTreeState:"clean", BuildDate:"2017-07-03T15:31:10Z", GoVersion:"go1.7.4", Compiler:"gc", Platform:"linux/amd64"}

Server Version: version.Info{Major:"1", Minor:"5", GitVersion:"v1.5.2", GitCommit:"269f928217957e7126dc87e6adfa82242bfe5b1e", GitTreeState:"clean", BuildDate:"2017-07-03T15:31:10Z", GoVersion:"go1.7.4", Compiler:"gc", Platform:"linux/amd64"}

[root@k8s_master ~]# cd /etc/kubernetes/

 [root@k8s_master kubernetes]# cat apiserver

###

# kubernetes system config

#

# The following values are used to configure the kube-apiserver

#

 

# The address on the local server to listen to.

KUBE_API_ADDRESS="--insecure-bind-address=0.0.0.0"

 

# The port on the local server to listen on.

KUBE_API_PORT="--port=8080"

 

# Port minions listen on

# KUBELET_PORT="--kubelet-port=10250"

 

# Comma separated list of nodes in the etcd cluster

KUBE_ETCD_SERVERS="--etcd-servers=http://192.168.138.130:2379"

 

# Address range to use for services

KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.254.0.0/16"

 

# default admission control policies

KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,ResourceQuota"

 

# Add your own!

KUBE_API_ARGS="--runtime-config=batch/v2alpha1=true"

[root@k8s_master ~]# for SERVICES in docker kube-apiserver kube-controller-manager kube-scheduler; do systemctl restart $SERVICES ; done

[root@k8s_master ~]# kubectl api-versions

apps/v1beta1

authentication.k8s.io/v1beta1

authorization.k8s.io/v1beta1

autoscaling/v1

batch/v1

batch/v2alpha1

certificates.k8s.io/v1alpha1

extensions/v1beta1

policy/v1beta1

rbac.authorization.k8s.io/v1alpha1

storage.k8s.io/v1beta1

v1

[root@k8s_master ~]# kubectl apply -f cronjob.yml

cronjob "hello" created

[root@k8s_master ~]# kubectl get cronjob

NAME      SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE

hello     */1 * * * *   False     0         Fri, 31 Aug 2018 07:38:00 +0800

[root@k8s_master ~]# kubectl get job

NAME               DESIRED   SUCCESSFUL   AGE

hello-1535671980   1         1            6m

hello-1535672040   1         1            5m

hello-1535672100   1         1            4m

hello-1535672160   1         1            3m

hello-1535672220   1         1            2m

hello-1535672280   1         1            1m

hello-1535672340   1         1            18s

[root@k8s_master ~]# kubectl get pod --show-all

NAME                                READY     STATUS      RESTARTS   AGE

hello-1535671980-t65n2              0/1       Completed   0          7m

hello-1535672040-grvbw              0/1       Completed   0          6m

hello-1535672100-0s9zw              0/1       Completed   0          5m

hello-1535672160-dbmkj              0/1       Completed   0          4m

hello-1535672220-v0lfr              0/1       Completed   0          3m

hello-1535672280-kq0cz              0/1       Completed   0          2m

hello-1535672340-9b2wz              0/1       Completed   0          1m

nginx-deployment-1851826925-f0gpc   1/1       Running     0          8h

nginx-deployment-1851826925-phw8g   1/1       Running     0          8h

nginx-deployment-1851826925-ttsqm   1/1       Running     0          8h

 

運行容器化應用是 Kubernetes 最重要的核心功能。爲知足不一樣的業務須要,Kubernetes提供了多種Controller,包括Deployment、DaemonSet、Job、CronJob等。

4.8 Service

咱們不該該指望Kubernetes Pod 是健壯的,而是要假設Pod中的容器極可能由於各類緣由發生故障而死掉。Deployment等controller 會經過動態建立和銷燬Pod來保證應用總體的健壯性。換句話說,Pod是脆弱的,但應用是健壯的。

每一個 Pod 都有本身的 IP 地址。當 controller用新Pod替代發生故障的 Pod 時,新 Pod 會分配到新的 IP 地址。這樣就產生了一個問題:

若是一組 Pod 對外提供服務(好比 HTTP),它們的 IP 頗有可能發生變化,那麼客戶端如何找到並訪問這個服務呢?

Kubernetes 給出的解決方案是Service。

Kubernetes Service從邏輯上表明瞭一組Pod,具體是哪些Pod則是由label來挑選。Service有本身IP,並且這個IP是不變的。客戶端只須要訪問Service的 IP,Kubernetes 則負責創建和維護Service與Pod的映射關係。不管後端 Pod 如何變化,對客戶端不會有任何影響,由於 Service 沒有變。

4.8.1 Ip service

[root@k8s_master ~]# kubectl apply -f http.yml

deployment "httpd" created

[root@k8s_master ~]# cat http.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: httpd

spec:

  replicas: 3

  template:

    metadata:

      labels:

        run: httpd

    spec:

      containers:

      - name: httpd

        image: httpd

        ports:

        - containerPort: 80

[root@k8s_master ~]# kubectl get pod

NAME                            READY     STATUS    RESTARTS   AGE

httpd-3937122862-00vn5          1/1       Running   0          8s

httpd-3937122862-7xbsp          1/1       Running   0          8s

httpd-3937122862-nkln3          1/1       Running   0          8s

node-exporter-daemonset-0b3zt   1/1       Running   1          5h

node-exporter-daemonset-mr3qp   1/1       Running   0          5h

node-exporter-daemonset-xl4gb   1/1       Running   0          5h

redis                           1/1       Running   4          15h

[root@k8s_master ~]# kubectl get pod -o wide

NAME                            READY     STATUS    RESTARTS   AGE       IP                NODE

httpd-3937122862-00vn5          1/1       Running   0          1m        172.8.18.2        192.168.138.161

httpd-3937122862-7xbsp          1/1       Running   0          1m        172.8.95.3        192.168.138.130

httpd-3937122862-nkln3          1/1       Running   0          1m        172.8.57.3        192.168.138.162

node-exporter-daemonset-0b3zt   1/1       Running   1          5h        192.168.138.130   192.168.138.130

node-exporter-daemonset-mr3qp   1/1       Running   0          5h        192.168.138.162   192.168.138.162

node-exporter-daemonset-xl4gb   1/1       Running   0          5h        192.168.138.161   192.168.138.161

redis                           1/1       Running   4          15h       172.8.95.2        192.168.138.130

[root@k8s_master ~]# curl 172.8.18.2

<html><body><h1>It works!</h1></body></html>

[root@k8s_master ~]# curl 172.8.95.3

<html><body><h1>It works!</h1></body></html>

[root@k8s_master ~]# curl 172.8.57.3

<html><body><h1>It works!</h1></body></html>

[root@k8s_master ~]# cat httpd_sv.yml

apiVersion: v1

kind: Service

metadata:

  name: httpd-svc

spec:

  type: NodePort

  selector:

    run: httpd

  ports:

  - protocol: TCP

    port: 8082

targetPort: 80

[root@k8s_master ~]# kubectl apply -f httpd_sv.yml

service "httpd-svc" created

[root@k8s_master ~]# kubectl get service

NAME         CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE

httpd-svc    10.254.192.170   <nodes>       8082:32304/TCP   21s

kubernetes   10.254.0.1       <none>        443/TCP          16h

[root@k8s_master ~]# curl 10.254.192.170:8082

<html><body><h1>It works!</h1></body></html>

httpd-svc分配到一個CLUSTER-IP 10.99.229.179。能夠經過該 IP 訪問後端的 httpd Pod。

經過 kubectl describe 能夠查看 httpd-svc 與 Pod 的對應關係

[root@k8s_master ~]# kubectl describe service httpd-svc

Name:           httpd-svc

Namespace:      default

Labels:         <none>

Selector:       run=httpd

Type:           NodePort

IP:         10.254.192.170

Port:           <unset> 8082/TCP

NodePort:       <unset> 32304/TCP

Endpoints:      172.8.18.3:80,172.8.57.4:80,172.8.95.4:80

Session Affinity:   None

No events.

[root@k8s_master ~]# iptables-save

能夠經過iptables-save命令打印出當前節點的iptables規則,由於輸出較多,這裏只截取與 httpd-svc Cluster IP10.99.229.179相關的信息:

這兩條規則的含義是:

若是 Cluster 內的 Pod(源地址來自 10.244.0.0/16)要訪問 httpd-svc,則容許。

其餘源地址訪問 httpd-svc,跳轉到規則 KUBE-SVC-RL3JAE4GN7VOGDGP。

# Generated by iptables-save v1.4.21 on Fri Aug 31 09:39:39 2018

-A KUBE-SERVICES -d 10.254.192.170/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8082 -j KUBE-SVC-RL3JAE4GN7VOGDGP

-A KUBE-SVC-NPX46M4PTMTKRN6Y -m comment --comment "default/kubernetes:https" -j KUBE-SEP-JHVFJXYDAZENHIRG

KUBE-SVC-RL3JAE4GN7VOGDGP 規則以下:

1/3 的機率跳轉到規則 KUBE-SEP-C5KB52P4BBJQ35PH。

1/3 的機率(剩下 2/3 的一半)跳轉到規則 KUBE-SEP-HGVKQQZZCF7RV4IT。

1/3 的機率跳轉到規則 KUBE-SEP-XE25WGVXLHEIRVO5。

上面三個跳轉的規則以下:

-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-QOREOV6IV3RD2ZSR

-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-U44RF5KARTOM77DD

-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -j KUBE-SEP-CAUY7XVTLK4ODWZ4

即將請求分別轉發到後端的三個 Pod。經過上面的分析,咱們獲得以下結論:

iptables 將訪問 Service 的流量轉發到後端 Pod,並且使用相似輪詢的負載均衡策略。

另外須要補充一點:Cluster 的每個節點都配置了相同的 iptables 規則,這樣就確保了整個Cluster都可以經過 Service的Cluster IP訪問Service。

4.8.2 K8s-dns

待補充

4.8.3 對外ip

除了 Cluster 內部能夠訪問 Service,不少狀況咱們也但願應用的 Service 可以暴露給 Cluster 外部。Kubernetes 提供了多種類型的 Service,默認是ClusterIP。

ClusterIP

Service 經過 Cluster 內部的 IP 對外提供服務,只有 Cluster 內的節點和 Pod 可訪問,這是默認的 Service 類型,前面實驗中的 Service 都是 ClusterIP。

NodePort

Service 經過 Cluster 節點的靜態端口對外提供服務。Cluster 外部能夠經過 <NodeIP>:<NodePort> 訪問 Service。

LoadBalancer

Service 利用 cloud provider 特有的 load balancer 對外提供服務,cloud provider 負責將 load balancer 的流量導向 Service。目前支持的 cloud provider 有 GCP、AWS、Azur 等。

下面咱們來實踐 NodePort,Service httpd-svc 的配置文件修改以下:

Kubernetes 依然會爲 httpd-svc 分配一個 ClusterIP,不一樣的是:

EXTERNAL-IP 爲 nodes,表示可經過 Cluster 每一個節點自身的 IP 訪問 Service。

PORT(S)爲 8082:32304。8082是 ClusterIP 監聽的端口,32304 則是節點上監聽的端口。Kubernetes會從30000-32767中分配一個可用的端口,每一個節點都會監聽此端口並將請求轉發給Service。

 

[root@k8s_master ~]# cat httpd_sv.yml

apiVersion: v1

kind: Service

metadata:

  name: httpd-svc

spec:

  type: NodePort

  selector:

    run: httpd

  ports:

  - protocol: TCP

    port: 8082

targetPort: 80

[root@k8s_master ~]# kubectl apply -f httpd_sv.yml

service "httpd-svc" created

[root@k8s_master ~]# kubectl get service

NAME         CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE

httpd-svc    10.254.192.170   <nodes>       8082:32304/TCP   46m

kubernetes   10.254.0.1       <none>        443/TCP          17h

[root@k8s_master ~]# netstat -lntup|grep 32304

tcp6       0      0 :::32304                :::*                    LISTEN      1138/kube-proxy    

[root@k8s_master ~]# curl 192.168.138.130:32304

<html><body><h1>It works!</h1></body></html>

[root@k8s_master ~]# curl 192.168.138.161:32304

<html><body><h1>It works!</h1></body></html>

[root@k8s_master ~]# curl 192.168.138.162:32304

<html><body><h1>It works!</h1></body></html>

NodePort 默認是的隨機選擇,不過咱們能夠用 nodePort 指定某個特定端口。

[root@k8s_master ~]# cat httpd_sv.yml

apiVersion: v1

kind: Service

metadata:

  name: httpd-svc

spec:

  type: NodePort

  selector:

    run: httpd

  ports:

  - protocol: TCP

    nodePort: 32100

    port: 8082

targetPort: 80

如今配置文件中就有三個 Port 了:

nodePort 是節點上監聽的端口。

port 是 ClusterIP 上監聽的端口。

targetPort 是 Pod 監聽的端口。

最終,Node和ClusterIP在各自端口上接收到的請求都會經過iptables轉發到Pod的targetPort。

[root@k8s_master ~]# kubectl apply -f httpd_sv.yml

service "httpd-svc" configured

[root@k8s_master ~]# netstat -lntup|grep 32100

tcp6       0      0 :::32100                :::*                    LISTEN      1138/kube-proxy    

[root@k8s_master ~]# curl 192.168.138.130:32100

<html><body><h1>It works!</h1></body></html>

[root@k8s_master ~]# curl 192.168.138.161:32100

<html><body><h1>It works!</h1></body></html>

[root@k8s_master ~]# curl 192.168.138.162:32100

<html><body><h1>It works!</h1></body></html>

4.9 Rolling&&update

[root@k8s_master ~]# cat httpd.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: httpd

spec:

  replicas: 2

  template:

    metadata:

      labels:

        run: httpd

    spec:

      containers:

      - name: httpd

        image: httpd

        ports:

        - containerPort: 80

 

[root@k8s_master ~]# kubectl apply -f httpd.yml

deployment "httpd" configured

[root@k8s_master ~]# kubectl get pod -o wide

NAME                            READY     STATUS    RESTARTS   AGE       IP                NODE

httpd-3457006022-0bsbg          1/1       Running   0          2h        172.8.18.3        192.168.138.161

httpd-3457006022-932jz          1/1       Running   0          2h        172.8.57.4        192.168.138.162

node-exporter-daemonset-0b3zt   1/1       Running   1          8h        192.168.138.130   192.168.138.130

node-exporter-daemonset-mr3qp   1/1       Running   0          8h        192.168.138.162   192.168.138.162

node-exporter-daemonset-xl4gb   1/1       Running   0          8h        192.168.138.161   192.168.138.161

redis                           1/1       Running   4          17h       172.8.95.2        192.168.138.130

[root@k8s_master ~]# cat httpd.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: httpd

spec:

  replicas: 2

  template:

    metadata:

      labels:

        run: httpd

    spec:

      containers:

      - name: httpd

        image: httpd:2.2.31

        ports:

        - containerPort: 80

 

[root@k8s_master ~]# kubectl apply -f httpd.yml

deployment "httpd" configured

[root@k8s_master ~]# kubectl get pod -o wide

NAME                            READY     STATUS    RESTARTS   AGE       IP                NODE

httpd-1478671140-c99t1          1/1       Running   0          5m        172.8.95.3        192.168.138.130

httpd-1478671140-zb8jc          1/1       Running   0          5m        172.8.18.2        192.168.138.161

[root@k8s_master ~]# docker ps

CONTAINER ID        IMAGE                                                        COMMAND                  CREATED             STATUS              PORTS               NAMES

f7314640b4c5        httpd:2.2.31                                                 "httpd-foreground"       5 minutes ago       Up 5 minutes                            k8s_httpd.1cd1feb7_httpd-1478671140-c99t1_default_65fc99ab-acd1-11e8-a26f-000c29cc726b_f457a097

[root@k8s_master ~]# kubectl get replicaset -o wide

NAME               DESIRED   CURRENT   READY     AGE       CONTAINER(S)   IMAGE(S)       SELECTOR

httpd-1478671140   2         2         2         8m        httpd          httpd:2.2.31   pod-template-hash=1478671140,run=httpd

httpd-3457006022   0         0         0         2h        httpd          httpd          pod-template-hash=3457006022,run=httpd

httpd-3510483496   0         0         0         2h        httpd          httpd          pod-template-hash=3510483496,run=httpd

httpd-3937122862   0         0         0         2h        httpd          httpd          pod-template-hash=3937122862,run=httpd

部署過程以下:

 

建立Deployment httpd

建立ReplicaSet httpd-1478671140

建立三個 Pod

當前鏡像爲 httpd

將配置文件中httpd替換爲 httpd:2.2.31,再次執行kubectl apply。

Deployment httpd 的鏡像更新爲 httpd:2.2.31

新建立了 ReplicaSet httpd-1276601241,鏡像爲 httpd:2.2.31,而且管理了三個新的 Pod。

以前的 ReplicaSet httpd-551879778 裏面已經沒有任何 Pod。

結論是:ReplicaSet httpd-551879778 的三個 httpd Pod 已經被 ReplicaSet httpd-1276601241 的三個 httpd:2.2.31 Pod 替換了。

 

具體過程能夠經過 kubectl describe deployment httpd 查看

[root@k8s_master ~]# kubectl describe deployment httpd

Name:           httpd

Namespace:      default

CreationTimestamp:  Fri, 31 Aug 2018 09:20:11 +0800

Labels:         run=httpd

Selector:       run=httpd

Replicas:       2 updated | 2 total | 2 available | 0 unavailable

StrategyType:      RollingUpdate

MinReadySeconds:    0

RollingUpdateStrategy:  1 max unavailable, 1 max surge

Conditions:

  Type      Status  Reason

  ----      ------  ------

  Available     True    MinimumReplicasAvailable

OldReplicaSets: <none>

NewReplicaSet:  httpd-1478671140 (2/2 replicas created)

Events:

  FirstSeen LastSeen    Count   From               SubObjectPath   Type        Reason          Message

  --------- --------    -----   ----               -------------   --------    ------          -------

  2h        12m     2   {deployment-controller }            Normal      ScalingReplicaSet   Scaled down replica set httpd-3457006022 to 2

  11m       11m     1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled up replica set httpd-1478671140 to 1

  11m       11m     1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled down replica set httpd-3457006022 to 1

  11m       11m     1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled up replica set httpd-1478671140 to 2

  10m       10m     1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled down replica set httpd-3457006022 to 0

每次只更新替換一個 Pod:

 

ReplicaSet httpd-1276601241 增長一個 Pod,總數爲 1。

ReplicaSet httpd-551879778 減小一個 Pod,總數爲 2。

ReplicaSet httpd-1276601241 增長一個 Pod,總數爲 2。

ReplicaSet httpd-551879778 減小一個 Pod,總數爲 1。

ReplicaSet httpd-1276601241 增長一個 Pod,總數爲 3。

ReplicaSet httpd-551879778 減小一個 Pod,總數爲 0。

每次替換的 Pod 數量是能夠定製的。Kubernetes 提供了兩個參數 maxSurge 和 maxUnavailable 來精細控制 Pod 的替換數量,咱們將在後面結合 Health Check 特性一塊兒討論。

 

金絲雀 把不一樣版本放到各自的deployment,經過同一個service訪問。具體例子https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments

https://www.jianshu.com/p/022685baba7d

記錄回滾

--record的做用是將當前命令記錄到revision記錄中,這樣咱們就能夠知道每一個revison對應的是哪一個配置文件。經過 kubectl rollout history deployment httpd 查看revison歷史記錄。

[root@k8s_master ~]# kubectl apply -f http2.yml --record

deployment "httpd" configured

[root@k8s_master ~]# kubectl apply -f http.yml --record

error: the path "http.yml" does not exist

[root@k8s_master ~]# kubectl apply -f httpd.yml --record

deployment "httpd" configured

[root@k8s_master ~]# kubectl rollout history deployment httpd

deployments "httpd"

REVISION    CHANGE-CAUSE

1       <none>

3       <none>

6       kubectl apply -f http2.yml --record

7       kubectl apply -f httpd.yml --record

CHANGE-CAUSE就是--record 的結果。若是要回滾到某個版本,好比revision 1,能夠執行命令 kubectl rollout undo deployment httpd --to-revision=1:

[root@k8s_master ~]# kubectl rollout undo deployment httpd --to-revision=6

deployment "httpd" rolled back

此時revison歷史記錄也會發生相應變化。

Revison7變成了revison8。不過咱們能夠經過 CHANGE-CAUSE 知道每一個 revison 的具體含義。因此必定要在執行 kubectl apply 時加上 --record參數。

[root@k8s_master ~]# kubectl rollout history deployment httpd

deployments "httpd"

REVISION    CHANGE-CAUSE

1       <none>

3       <none>

7       kubectl apply -f httpd.yml --record

8       kubectl apply -f http2.yml --record

 

4.10 Health check

強大的自愈能力是Kubernetes 這類容器編排引擎的一個重要特性。自愈的默認實現方式是自動重啓發生故障的容器。除此以外,用戶還能夠利用Liveness 和Readiness 探測機制設置更精細的健康檢查,進而實現以下需求:

零停機部署。

避免部署無效的鏡像。

更加安全的滾動升級。

下面經過實踐學習 Kubernetes 的 Health Check 功能。

咱們首先學習Kubernetes默認的健康檢查機制:

每一個容器啓動時都會執行一個進程,此進程由Dockerfile的CMD或ENTRYPOINT 指定。

若是進程退出時返回碼非零,則認爲容器發生故障,Kubernetes就會根據restartPolicy重啓容器。

 

下面咱們模擬一個容器發生故障的場景,Pod 配置文件以下:

[root@k8s_master ~]# cat healthcheck.yml

apiVersion: v1

kind: Pod

metadata:

  labels:

    test: healthcheck

  name: healthcheck

spec:

  restartPolicy: OnFailure

  containers:

  - name: healthcheck

    image: busybox

    args:

    - /bin/sh

    - -c

    - sleep 10; exit 1

[root@k8s_master ~]# kubectl apply -f healthcheck.yml --record

pod "healthcheck" created

Pod 的 restartPolicy 設置爲 OnFailure,默認爲 Always。

sleep 10; exit 1模擬容器啓動10秒後發生故障。

執行kubectl apply建立 Pod,命名爲healthcheck。

[root@k8s_master ~]# kubectl get pod healthcheck

NAME          READY     STATUS    RESTARTS   AGE

healthcheck   0/1       Error     3          1m

在上面的例子中,容器進程返回值非零,Kubernetes則認爲容器發生故障,須要重啓。

但有很多狀況是發生了故障,但進程並不會退出。

好比訪問Web服務器時顯示500內部錯誤,多是系統超載,也多是資源死鎖,此時httpd進程並無異常退出,在這種狀況下重啓容器多是最直接最有效的解決方案,那咱們如何利用Health Check機制來處理這類場景呢?

4.10.1 lineness

Liveness探測讓用戶能夠自定義判斷容器是否健康的條件。若是探測失敗,Kubernetes就會重啓容器。

仍是舉例說明,建立以下Pod:

[root@k8s_master ~]# cat liveness.yml

apiVersion: v1

kind: Pod

metadata:

  labels:

    test: liveness

  name: liveness

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

      initialDelaySeconds: 10

      periodSeconds: 5

啓動進程首先建立文件 /tmp/healthy,30 秒後刪除,在咱們的設定中,若是 /tmp/healthy 文件存在,則認爲容器處於正常狀態,反正則發生故障。

livenessProbe 部分定義如何執行 Liveness 探測:

探測的方法是:經過 cat 命令檢查 /tmp/healthy 文件是否存在。若是命令執行成功,返回值爲零,Kubernetes 則認爲本次 Liveness 探測成功;若是命令返回值非零,本次 Liveness 探測失敗。

initialDelaySeconds: 10 指定容器啓動 10 以後開始執行 Liveness 探測,咱們通常會根據應用啓動的準備時間來設置。好比某個應用正常啓動要花 30 秒,那麼 initialDelaySeconds 的值就應該大於 30。

periodSeconds: 5 指定每 5 秒執行一次 Liveness 探測。Kubernetes 若是連續執行 3 次 Liveness 探測均失敗,則會殺掉並重啓容器。

下面建立 Pod liveness:

[root@k8s_master ~]# kubectl apply -f liveness.yml

pod "liveness" created

從配置文件可知,最開始的30秒,/tmp/healthy存在,cat命令返回0,Liveness探測成功,這段時間 kubectl describe pod liveness的Events部分會顯示正常的日誌。

[root@k8s_master ~]# kubectl describe pod liveness

Events:

  FirstSeen LastSeen    Count   From               SubObjectPath          Type        Reason          Message

  --------- --------    -----   ----               -------------          --------    ------          -------

  1m        1m      1   {kubelet 192.168.138.161}   spec.containers{liveness}    Normal      Pulling         pulling image "busybox"

  1m        1m      2   {kubelet 192.168.138.161}                  Warning     MissingClusterDNS   kubelet does not have ClusterDNS IP configured and cannot create Pod using "ClusterFirst" policy. Falling back to DNSDefault policy.

  1m        1m      1   {kubelet 192.168.138.161}   spec.containers{liveness}    Normal      Pulled          Successfully pulled image "busybox"

  1m        1m      1   {kubelet 192.168.138.161}   spec.containers{liveness}    Normal      Created         Created container with docker id d86661d05d5d; Security:[seccomp=unconfined]

  1m        1m      1   {kubelet 192.168.138.161}   spec.containers{liveness}    Normal      Started         Started container with docker id d86661d05d5d

  27s       27s     1   {default-scheduler }                       Normal      Scheduled       Successfully assigned liveness to 192.168.138.161

35秒以後,日誌會顯示/tmp/healthy已經不存在,Liveness 探測失敗。再過幾十秒,幾回探測都失敗後,容器會被重啓。

Events:

  FirstSeen LastSeen    Count   From               SubObjectPath          Type        Reason      Message

  --------- --------    -----   ----               -------------          --------    ------      -------

  2m        2m      1   {kubelet 192.168.138.161}   spec.containers{liveness}    Normal      Created     Created container with docker id d86661d05d5d; Security:[seccomp=unconfined]

  2m        2m      1   {kubelet 192.168.138.161}   spec.containers{liveness}    Normal      Started     Started container with docker id d86661d05d5d

  1m        1m      3   {kubelet 192.168.138.161}   spec.containers{liveness}    Warning     Unhealthy   Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory

 

  1m    1m  1   {default-scheduler }                       Normal  Scheduled       Successfully assigned liveness to 192.168.138.161

  2m    1m  2   {kubelet 192.168.138.161}   spec.containers{liveness}   Normal    Pulling         pulling image "busybox"

  1m    1m  1   {kubelet 192.168.138.161}   spec.containers{liveness}   Normal    Killing         Killing container with docker id d86661d05d5d: pod "liveness_default(415f53a6-acdd-11e8-a26f-000c29cc726b)" container "liveness" is unhealthy, it will be killed and re-created.

  2m    1m  2   {kubelet 192.168.138.161}   spec.containers{liveness}   Normal    Pulled          Successfully pulled image "busybox"

  2m    1m  3   {kubelet 192.168.138.161}                  Warning    MissingClusterDNS   kubelet does not have ClusterDNS IP configured and cannot create Pod using "ClusterFirst" policy. Falling back to DNSDefault policy.

  1m    1m  1   {kubelet 192.168.138.161}   spec.containers{liveness}   Normal    Created         Created container with docker id d166192948c7; Security:[seccomp=unconfined]

  1m    1m  1   {kubelet 192.168.138.161}   spec.containers{liveness}   Normal    Started         Started container with docker id d166192948c7

[root@k8s_master ~]# kubectl get pod liveness

NAME       READY     STATUS    RESTARTS   AGE

liveness   1/1       Running   3          4m

4.10.2 readiness

用戶經過Liveness探測能夠告訴Kubernetes何時經過重啓容器實現自愈;Readiness 探測則是告訴 Kubernetes何時能夠將容器加入到Service負載均衡池中,對外提供服務。

Readiness探測的配置語法與Liveness探測徹底同樣,下面是個例子:

[root@k8s_master ~]# cat readiness.yml

apiVersion: v1

kind: Pod

metadata:

  labels:

    test: readiness

  name: readiness

spec:

  restartPolicy: OnFailure

  containers:

  - name: readiness

    image: busybox

    args:

    - /bin/sh

    - -c

    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600

    readinessProbe:

      exec:

        command:

        - cat

        - /tmp/healthy

      initialDelaySeconds: 10

      periodSeconds: 5

[root@k8s_master ~]# kubectl apply -f readiness.yml

pod "readiness" created

[root@k8s_master ~]# kubectl get pod readiness

NAME        READY     STATUS    RESTARTS   AGE

readiness   1/1       Running   0          47s

[root@k8s_master ~]# kubectl get pod readiness

NAME        READY     STATUS    RESTARTS   AGE

readiness   0/1       Running   0          54s

 

Pod readiness 的 READY 狀態經歷了以下變化:

 

剛被建立時,READY 狀態爲不可用。

15 秒後(initialDelaySeconds + periodSeconds),第一次進行 Readiness 探測併成功返回,設置 READY 爲可用。

30 秒後,/tmp/healthy 被刪除,連續 3 次 Readiness 探測均失敗後,READY 被設置爲不可用。

[root@k8s_master ~]#  kubectl describe pod readiness

Name:       readiness

Namespace:  default

Node:       192.168.138.130/192.168.138.130

Start Time: Fri, 31 Aug 2018 13:32:25 +0800

Labels:     test=readiness

Status:     Running

IP:     172.8.95.4

Events:

  FirstSeen LastSeen    Count   From               SubObjectPath          Type        Reason          Message

  --------- --------    -----   ----               -------------          --------    ------          -------

  2m        2m      1   {default-scheduler }                       Normal      Scheduled       Successfully assigned readiness to 192.168.138.130

  2m        2m      1   {kubelet 192.168.138.130}   spec.containers{readiness}    Normal      Pulling         pulling image "busybox"

  2m        2m      2   {kubelet 192.168.138.130}                  Warning     MissingClusterDNS   kubelet does not have ClusterDNS IP configured and cannot create Pod using "ClusterFirst" policy. Falling back to DNSDefault policy.

  2m        2m      1   {kubelet 192.168.138.130}   spec.containers{readiness}    Normal      Pulled          Successfully pulled image "busybox"

  2m        2m      1   {kubelet 192.168.138.130}   spec.containers{readiness}    Normal      Created         Created container with docker id 265ac2f4e37a; Security:[seccomp=unconfined]

  2m        2m      1   {kubelet 192.168.138.130}   spec.containers{readiness}    Normal      Started         Started container with docker id 265ac2f4e37a

  1m        2s      19  {kubelet 192.168.138.130}   spec.containers{readiness}    Warning     Unhealthy       Readiness probe failed: cat: can't open '/tmp/healthy': No such file or directory

Liveness探測和Readiness探測是兩種 Health Check 機制,若是不特地配置,Kubernetes 將對兩種探測採起相同的默認行爲,即經過判斷容器啓動進程的返回值是否爲零來判斷探測是否成功。

兩種探測的配置方法徹底同樣,支持的配置參數也同樣。不一樣之處在於探測失敗後的行爲:Liveness 探測是重啓容器;Readiness 探測則是將容器設置爲不可用,不接收 Service 轉發的請求。

Liveness 探測和 Readiness 探測是獨立執行的,兩者之間沒有依賴,因此能夠單獨使用,也能夠同時使用。用 Liveness 探測判斷容器是否須要重啓以實現自愈;用 Readiness 探測判斷容器是否已經準備好對外提供服務

readiness的做用是有些服務須要一段時間的預熱,對於這種服務,若是不設置readiness,就永遠不可用

4.10.3 scale up && health

對於多副本應用,當執行 Scale Up 操做時,新副本會做爲 backend 被添加到 Service 的負責均衡中,與已有副本一塊兒處理客戶的請求。考慮到應用啓動一般都須要一個準備階段,好比加載緩存數據,鏈接數據庫等,從容器啓動到正真可以提供服務是須要一段時間的。咱們能夠經過 Readiness探測判斷容器是否就緒,避免將請求發送到尚未 ready 的 backend。

[root@k8s_master ~]# kubectl apply -f httpd_health.yml --record

deployment "httpd" created

service "httpd-svc" created

[root@k8s_master ~]# cat httpd_health.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: httpd

spec:

  replicas: 3

  template:

    metadata:

      labels:

        run: httpd

    spec:

      containers:

      - name: httpd

        image: httpd:2.2.31

        ports:

        - containerPort: 80

        readinessProbe:

          httpGet:

            scheme: HTTP

            path: /

            port: 80

          initialDelaySeconds: 10

          periodSeconds: 5

 

---

 

apiVersion: v1

kind: Service

metadata:

  name: httpd-svc

spec:

  type: NodePort

  selector:

    run: httpd

  ports:

  - protocol: TCP

    nodePort: 30000

    port: 8082

targetPort: 80

上面配置的做用是:

 

容器啓動 10 秒以後開始探測。

若是 http://[container_ip]:8080/healthy 返回代碼不是 200-400,表示容器沒有就緒,不接收 Service web-svc 的請求。

每隔 5 秒再探測一次。

直到返回代碼爲 200-400,代表容器已經就緒,而後將其加入到 web-svc 的負責均衡中,開始處理客戶請求。

探測會繼續以 5 秒的間隔執行,若是連續發生 3 次失敗,容器又會從負載均衡中移除,直到下次探測成功從新加入。

對於生產環境中重要的應用都建議配置 Health Check,保證處理客戶請求的容器都是準備就緒的 Service backend。

[root@k8s_master ~]# kubectl get pod -o wide

NAME                            READY     STATUS             RESTARTS   AGE       IP                NODE

healthcheck                     0/1       CrashLoopBackOff   14         54m       172.8.95.3        192.168.138.130

httpd-1649560772-0ggrh          1/1       Running            0          2m        172.8.18.3        192.168.138.161

httpd-1649560772-qm2vf          1/1       Running            0          2m        172.8.57.3        192.168.138.162

httpd-1649560772-r26zv          1/1       Running            0          2m        172.8.95.4        192.168.138.130

liveness                        1/1       Running            15         41m       172.8.18.2        192.168.138.161

node-exporter-daemonset-0b3zt   1/1       Running            1          10h       192.168.138.130   192.168.138.130

[root@k8s_master ~]# kubectl describe svc httpd-svc

Name:           httpd-svc

Namespace:      default

Labels:         <none>

Selector:       run=httpd

Type:           NodePort

IP:         10.254.76.56

Port:           <unset> 8082/TCP

NodePort:       <unset> 30000/TCP

Endpoints:      172.8.18.3:80,172.8.57.3:80,172.8.95.4:80

Session Affinity:   None

No events.

 

4.10.4 在滾動更新中使用

現有一個正常運行的多副本應用,接下來對應用進行更新(好比使用更高版本的 image),Kubernetes 會啓動新副本,而後發生了以下事件:

正常狀況下新副本須要 10 秒鐘完成準備工做,在此以前沒法響應業務請求。

但因爲人爲配置錯誤,副本始終沒法完成準備工做(好比沒法鏈接後端數據庫)。

先別繼續往下看,如今請花一分鐘思考這個問題:若是沒有配置 Health Check,會出現怎樣的狀況?

由於新副本自己沒有異常退出,默認的 Health Check 機制會認爲容器已經就緒,進而會逐步用新副本替換現有副本,其結果就是:當全部舊副本都被替換後,整個應用將沒法處理請求,沒法對外提供服務。若是這是發生在重要的生產系統上,後果會很是嚴重。

若是正確配置了 Health Check,新副本只有經過了 Readiness 探測,纔會被添加到 Service;若是沒有經過探測,現有副本不會被所有替換,業務仍然正常進行。

下面經過例子來實踐 Health Check 在 Rolling Update 中的應用。

用以下配置文件 app.v1.yml 模擬一個 10 副本的應用:

[root@k8s_master ~]# kubectl apply -f app.v1.yml --record

deployment "app" created

[root@k8s_master ~]# kubectl get deployment app

NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

app       10        10        10           0           10s

 [root@k8s_master ~]# kubectl get deployment app

NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

app       10        10        10           10          47s

[root@k8s_master ~]# cat app.v1.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: app

spec:

  replicas: 10

  template:

    metadata:

      labels:

        run: app

    spec:

      containers:

      - name: app

        image: busybox

        args:

        - /bin/sh

        - -c

        - sleep 10; touch /tmp/healthy; sleep 30000

        readinessProbe:

          exec:

            command:

            - cat

            - /tmp/healthy

          initialDelaySeconds: 10

          periodSeconds: 5

[root@k8s_master ~]# kubectl get pod

NAME                            READY     STATUS             RESTARTS   AGE

app-3748071646-0fsct            1/1       Running            0          3m

app-3748071646-0gdm1            1/1       Running            0          3m

app-3748071646-1ftd4            1/1       Running            0          3m

app-3748071646-242cv            1/1       Running            0          3m

app-3748071646-281v1            1/1       Running            0          3m

app-3748071646-7j3wg            1/1       Running            0          3m

app-3748071646-8dd35            1/1       Running            0          3m

app-3748071646-msq6v            1/1       Running            0          3m

app-3748071646-rd17j            1/1       Running            0          3m

app-3748071646-xqqjk            1/1       Running            0          3m

[root@k8s_master ~]# kubectl get deployment app

NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

app       10        10        10           10          37s

很顯然,因爲新副本中不存在 /tmp/healthy,是沒法經過 Readiness 探測的。驗證以下:

[root@k8s_master ~]# kubectl apply -f app.v2.yml --record

deployment "app" configured

[root@k8s_master ~]# cat app.v2.yml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: app

spec:

  replicas: 10

  template:

    metadata:

      labels:

        run: app

    spec:

      containers:

      - name: app

        image: busybox

        args:

        - /bin/sh

        - -c

        - sleep 30000

        readinessProbe:

          exec:

            command:

            - cat

            - /tmp/healthy

          initialDelaySeconds: 10

          periodSeconds: 5

[root@k8s_master ~]# kubectl get deployment app

NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

app       10        11        2            9           57s

[root@k8s_master ~]# kubectl get pod

NAME                            READY     STATUS             RESTARTS   AGE

app-2073951902-2wld1            0/1       Running            0          21s

app-2073951902-mqw85            0/1       Running            0          21s

app-3748071646-3lk5z            1/1       Running            0          1m

app-3748071646-548ww            1/1       Terminating        0          1m

app-3748071646-fj9zz            1/1       Running            0          1m

app-3748071646-kh4xl            1/1       Running            0          1m

app-3748071646-nxrdh            1/1       Running            0          1m

app-3748071646-p5p86            1/1       Running            0          1m

app-3748071646-pcrww            1/1       Running            0          1m

app-3748071646-pwd2r            1/1       Running            0          1m

app-3748071646-sgqzx            1/1       Running            0          1m

一直保持

[root@k8s_master ~]# kubectl get deployment app

NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

app       10        11        2            9           4m

很顯然,因爲新副本中不存在 /tmp/healthy,是沒法經過 Readiness 探測的。驗證以下:

這個截圖包含了大量的信息,值得咱們詳細分析。

先關注 kubectl get pod 輸出:

從 Pod 的 AGE 欄可判斷,最後 5 個 Pod 是新副本,目前處於 NOT READY 狀態。

舊副本從最初 10 個減小到 8 個。

再來看 kubectl get deployment app 的輸出:

DESIRED 10 表示指望的狀態是 10 個 READY 的副本。

CURRENT 13 表示當前副本的總數:即 8 箇舊副本 + 5 個新副本。

UP-TO-DATE 5 表示當前已經完成更新的副本數:即 5 個新副本。

AVAILABLE 8 表示當前處於 READY 狀態的副本數:即 8箇舊副本。

在咱們的設定中,新副本始終都沒法經過 Readiness 探測,因此這個狀態會一直保持下去。

上面咱們模擬了一個滾動更新失敗的場景。不過幸運的是:Health Check 幫咱們屏蔽了有缺陷的副本,同時保留了大部分舊副本,業務沒有因更新失敗受到影響。

接下來咱們要回答:爲何新建立的副本數是 5 個,同時只銷毀了 2 箇舊副本?

緣由是:滾動更新經過參數 maxSurge 和 maxUnavailable 來控制副本替換的數量。

 

maxSurge

此參數控制滾動更新過程當中副本總數的超過 DESIRED 的上限。maxSurge 能夠是具體的整數(好比 3),也能夠是百分百,向上取整。maxSurge 默認值爲 25%。

 

在上面的例子中,DESIRED 爲 10,那麼副本總數的最大值爲:

roundUp(10 + 10 * 25%) = 13

 

因此咱們看到 CURRENT 就是 13。

 

maxUnavailable

此參數控制滾動更新過程當中,不可用的副本相佔 DESIRED 的最大比例。 maxUnavailable 能夠是具體的整數(好比 3),也能夠是百分百,向下取整。maxUnavailable 默認值爲 25%。

 

在上面的例子中,DESIRED 爲 10,那麼可用的副本數至少要爲:

10 - roundDown(10 * 25%) = 8

 

因此咱們看到 AVAILABLE 就是 8。

 

maxSurge 值越大,初始建立的新副本數量就越多;maxUnavailable 值越大,初始銷燬的舊副本數量就越多。

 

理想狀況下,咱們這個案例滾動更新的過程應該是這樣的:

 

首先建立 3 個新副本使副本總數達到 13 個。

而後銷燬 2 箇舊副本使可用的副本數降到 8 個。

當這 2 箇舊副本成功銷燬後,可再建立 2 個新副本,使副本總數保持爲 13 個。

當新副本經過 Readiness 探測後,會使可用副本數增長,超過 8。

進而能夠繼續銷燬更多的舊副本,使可用副本數回到 8。

舊副本的銷燬使副本總數低於 13,這樣就容許建立更多的新副本。

這個過程會持續進行,最終全部的舊副本都會被新副本替換,滾動更新完成。

而咱們的實際狀況是在第 4 步就卡住了,新副本沒法經過 Readiness 探測。這個過程能夠在 kubectl describe deployment app 的日誌部分查看。

[root@k8s_master ~]#  kubectl describe deployment app

Name:           app

Namespace:      default

CreationTimestamp:  Fri, 31 Aug 2018 14:31:26 +0800

Labels:         run=app

Selector:       run=app

Replicas:       2 updated | 10 total | 9 available | 2 unavailable

StrategyType:      RollingUpdate

MinReadySeconds:    0

RollingUpdateStrategy:  1 max unavailable, 1 max surge

Conditions:

 

  Type      Status  Reason

  ----      ------  ------

  Available     True    MinimumReplicasAvailable

OldReplicaSets: app-3748071646 (9/9 replicas created)

NewReplicaSet:  app-2073951902 (2/2 replicas created)

Events:

  FirstSeen LastSeen    Count   From               SubObjectPath   Type        Reason          Message

  --------- --------    -----   ----               -------------   --------    ------          -------

  9m        9m      1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled up replica set app-3748071646 to 10

  9m        9m      1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled up replica set app-2073951902 to 1

  9m        9m      1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled down replica set app-3748071646 to 9

  9m        9m      1   {deployment-controller }            Normal      ScalingReplicaSet   Scaled up replica set app-2073951902 to 2

若是滾動更新失敗,能夠經過 kubectl rollout undo 回滾到上一個版本

[root@k8s_master ~]# kubectl rollout history deployment app

deployments "app"

REVISION    CHANGE-CAUSE

1       kubectl apply -f app.v1.yml --record

2       kubectl apply -f app.v2.yml –record

[root@k8s_master ~]# kubectl rollout undo deployment app --to-revision=1

deployment "app" rolled back

[root@k8s_master ~]# kubectl get deployment app

NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

app       10        10        10           10          13m

若是要定製 maxSurge 和 maxUnavailable,能夠以下配置:

 

 

 

4.11 數據管理

首先咱們會學習Volume,以及 Kubernetes 如何經過 Volume 爲集羣中的容器提供存儲;而後咱們會實踐幾種經常使用的Volume 類型並理解它們各自的應用場景;最後,咱們會討論Kubernetes如何經過Persistent Volume和Persistent Volume Claim分離集羣管理員與集羣用戶的職責,並實踐 Volume 的靜態供給和動態供給。

本節咱們討論 Kubernetes 的存儲模型 Volume,學習如何將各類持久化存儲映射到容器。

 

咱們常常會說:容器和 Pod 是短暫的。

其含義是它們的生命週期可能很短,會被頻繁地銷燬和建立。容器銷燬時,保存在容器內部文件系統中的數據都會被清除。

爲了持久化保存容器的數據,可使用 Kubernetes Volume。

Volume的生命週期獨立於容器,Pod 中的容器可能被銷燬和重建,但 Volume 會被保留。

本質上,Kubernetes Volume 是一個目錄,這一點與 Docker Volume 相似。當 Volume 被 mount 到 Pod,Pod 中的全部容器均可以訪問這個 Volume。Kubernetes Volume 也支持多種 backend 類型,包括 emptyDir、hostPath、GCE Persistent Disk、AWS Elastic Block Store、NFS、Ceph 等,完整列表可參考 https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes

Volume 提供了對各類 backend 的抽象,容器在使用 Volume 讀寫數據的時候不須要關心數據究竟是存放在本地節點的文件系統中呢仍是雲硬盤上。對它來講,全部類型的 Volume 都只是一個目錄。

咱們將從最簡單的 emptyDir 開始學習 Kubernetes Volume。

emptyDir 是最基礎的 Volume 類型。正如其名字所示,一個 emptyDir Volume 是 Host 上的一個空目錄。

emptyDir Volume 對於容器來講是持久的,對於 Pod 則不是。當 Pod 從節點刪除時,Volume 的內容也會被刪除。但若是隻是容器被銷燬而 Pod 還在,則 Volume 不受影響。

也就是說:emptyDir Volume 的生命週期與 Pod 一致。

Pod 中的全部容器均可以共享 Volume,它們能夠指定各自的mount路徑。下面經過例子來實踐emptyDir,配置文件以下

[root@k8s_master ~]# cat volume.yml

apiVersion: v1

kind: Pod

metadata:

  name: producer-consumer

spec:

  containers:

  - image: busybox

    name: producer

    volumeMounts:

    - mountPath: /producer_dir

      name: shared-volume

    args:

    - /bin/sh

    - -c

    - echo "hello world" > /producer_dir/hello; sleep 30000

 

  - image: busybox

    name: consumer

    volumeMounts:

    - mountPath: /consumer_dir

      name: shared-volume

    args:

    - /bin/sh

    - -c

    - cat /consumer_dir/hello; sleep 30000

 

  volumes:

  - name: shared-volume

emptyDir: {}

這裏咱們模擬了一個 producer-consumer 場景。Pod 有兩個容器 producer和 consumer,它們共享一個 Volume。producer 負責往 Volume 中寫數據,consumer 則是從 Volume 讀取數據。

文件最底部 volumes 定義了一個 emptyDir 類型的 Volume shared-volume。

producer 容器將 shared-volume mount 到 /producer_dir 目錄。

producer 經過 echo 將數據寫到文件 hello 裏。

consumer 容器將 shared-volume mount 到 /consumer_dir 目錄。

consumer 經過 cat 從文件 hello 讀數據。

執行以下命令建立 Pod:

[root@k8s_master ~]# kubectl get pod

NAME                            READY     STATUS             RESTARTS   AGE

producer-consumer               2/2       Running            0          21s

[root@k8s_master ~]# kubectl logs producer-consumer consumer

hello world

kubectl logs 顯示容器 consumer 成功讀到了 producer 寫入的數據,驗證了兩個容器共享 emptyDir Volume。

由於 emptyDir 是 Docker Host 文件系統裏的目錄,其效果至關於執行了 docker run -v /producer_dir 和 docker run -v /consumer_dir。經過 docker inspect 查看容器的詳細配置信息,咱們發現兩個容器都 mount 了同一個目錄:

Docker inspect 容器id1

"Mounts": [

            {

                "Type": "bind",

                "Source": "/var/lib/kubelet/pods/d37d1a69-aced-11e8-a26f-000c29cc726b/volumes/kubernetes.io~empty-dir/shared-volume",

                "Destination": "/consumer_dir",

                "Mode": "",

                "RW": true,

                "Propagation": "rprivate"

            },

Docker inspect 容器id1

      {

                "Type": "bind",

                "Source": "/var/lib/kubelet/pods/d37d1a69-aced-11e8-a26f-000c29cc726b/volumes/kubernetes.io~empty-dir/shared-volume",

                "Destination": "/producer_dir",

                "Mode": "",

                "RW": true,

                "Propagation": "rprivate"

Hostpath

[root@k8s_master ~]# cat webserver.yml

apiVersion: v1

kind: Pod

metadata:

  name: test-pd

spec:

  containers:

  - image: test-webserver:123

    name: test-container

    volumeMounts:

    - mountPath: /test-pd

      name: test-volume

  volumes:

  - name: test-volume

    hostPath:

      # directory location on host

      path: /data

docker ps

gcr.io/google_containers/test-webserver               latest              25906c5a72ed        3 years ago              4.53 MB

test-webserver                                        123                 25906c5a72ed        3 years ago              4.53 MB

google鏡像默認要下載,更更名稱就能夠了

[root@k8s_master ~]# kubectl apply -f webserver.yml

pod "test-pd" created

[root@k8s_master ~]# kubectl describe pod test-pd

Name:       test-pd

Namespace:  default

Node:       192.168.138.162/192.168.138.162

Start Time: Fri, 31 Aug 2018 18:14:57 +0800

Labels:     <none>

Status:     Running

IP:     172.8.57.4

Controllers:    <none>

Containers:

  test-container:

    Container ID:    docker://bb0bd6782853a168a7cac045ebc5986a39f9669a5923e847f751b79d46d25729

    Image:      test-webserver:123

    Image ID:   docker://sha256:25906c5a72eda6a5007c54bc9bd3ec39e446ef2289c95deb3abe53a0e115e0f0

    Port:      

    State:      Running

      Started:      Fri, 31 Aug 2018 18:14:58 +0800

    Ready:      True

    Restart Count:  0

    Volume Mounts:

      /test-pd from test-volume (rw)

[root@k8s_master ~]# kubectl get pods

NAME                            READY     STATUS             RESTARTS   AGE

healthcheck                     0/1       CrashLoopBackOff   65         5h

test-pd                         1/1       Running            0          9m

[root@k8s_client2 data]#  curl 172.8.57.4

<pre>

<a href="test-pd/">test-pd/</a>

<a href="run/">run/</a>

<a href="etc/">etc/</a>

<a href="dev/">dev/</a>

<a href="sys/">sys/</a>

<a href="proc/">proc/</a>

<a href=".dockerenv">.dockerenv</a>

<a href="test-webserver">test-webserver</a>

</pre>

[root@k8s_client2 data]#  curl 172.8.57.4/test-pd/1.html

houpj

[root@k8s_client2 data]# cat 1.html

Houpj

Ceph、GlusterFS 後續補充

 

4.12 PV&&PVC

Volume 提供了很是好的數據持久化方案,不過在可管理性上還有不足。

 

拿前面 AWS EBS 的例子來講,要使用 Volume,Pod 必須事先知道以下信息:

當前 Volume 來自 AWS EBS。

EBS Volume 已經提早建立,而且知道確切的 volume-id。

Pod 一般是由應用的開發人員維護,而 Volume 則一般是由存儲系統的管理員維護。開發人員要得到上面的信息:

要麼詢問管理員。

要麼本身就是管理員。

這樣就帶來一個管理上的問題:應用開發人員和系統管理員的職責耦合在一塊兒了。若是系統規模較小或者對於開發環境這樣的狀況還能夠接受。但當集羣規模變大,特別是對於生成環境,考慮到效率和安全性,這就成了必需要解決的問題。

Kubernetes 給出的解決方案是 PersistentVolume 和 PersistentVolumeClaim。[pə'zɪstənt][klem]

PersistentVolume (PV) 是外部存儲系統中的一塊存儲空間,由管理員建立和維護。與 Volume 同樣,PV 具備持久性,生命週期獨立於 Pod。

PersistentVolumeClaim (PVC) 是對 PV 的申請 (Claim)。PVC 一般由普通用戶建立和維護。須要爲 Pod 分配存儲資源時,用戶能夠建立一個 PVC,指明存儲資源的容量大小和訪問模式(好比只讀)等信息,Kubernetes 會查找並提供知足條件的 PV。

有了 PersistentVolumeClaim,用戶只須要告訴 Kubernetes 須要什麼樣的存儲資源,而沒必要關心真正的空間從哪裏分配,如何訪問等底層細節信息。這些 Storage Provider 的底層信息交給管理員來處理,只有管理員才應該關心建立 PersistentVolume 的細節信息。

Kubernetes 支持多種類型的 PersistentVolume,好比 AWS EBS、Ceph、NFS 等

[root@k8s_master ~]# cat nfs-pv1.yml

apiVersion: v1

kind: PersistentVolume

metadata:

  name: mypv1

spec:

  capacity:

    storage: 1Gi

  accessModes:

    - ReadWriteOnce

  persistentVolumeReclaimPolicy: Recycle

#  storageClassName: nfs  #不支持

  nfs:

    path: /nfsdata/pv1  #指定 PV 在 NFS 服務器上對應的目錄。

    server: 192.168.138.130

#下面的也正確

#apiVersion: v1

#kind: PersistentVolume

#metadata:

#  name: nfs

#spec:

#  capacity:

#    storage: 1Mi

#  accessModes:

#    - ReadWriteMany

#  nfs:

#    server: 192.168.138.130

#    path: "/nfsdata/pv1"

capacity 指定 PV 的容量爲 1G。

 

accessModes 指定訪問模式爲 ReadWriteOnce,支持的訪問模式有:

ReadWriteOnce – PV 能以 read-write 模式 mount 到單個節點。

ReadOnlyMany – PV 能以 read-only 模式 mount 到多個節點。

ReadWriteMany – PV 能以 read-write 模式 mount 到多個節點。

 

persistentVolumeReclaimPolicy 指定當 PV 的回收策略爲 Recycle,支持的策略有:

Retain – 須要管理員手工回收。

Recycle – 清除 PV 中的數據,效果至關於執行 rm -rf /thevolume/*。

Delete – 刪除 Storage Provider 上的對應存儲資源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。

 

storageClassName 指定 PV 的 class 爲 nfs。至關於爲 PV 設置了一個分類,PVC 能夠指定 class 申請相應 class 的 PV,這個版本不支持;

 

[root@k8s_master ~]# kubectl apply -f nfs-pv1.yml

error: error validating "nfs-pv1.yml": error validating data: found invalid field storageClassName for v1.PersistentVolumeSpec; if you choose to ignore these errors, turn validation off with --validate=false

 [root@k8s_master ~]# kubectl apply -f nfs-pv1.yml

persistentvolume "mypv1" configured

[root@k8s_master ~]# kubectl get pv

NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM     REASON    AGE

mypv1     1Gi        RWO           Recycle         Available                       3m

nfs       1Mi        RWX           Retain          Available                       5m

[root@k8s_master ~]# kubectl apply -f nfs-pvc1.yml

persistentvolumeclaim "mypvc1" created

[root@k8s_master ~]# kubectl get pvc

NAME      STATUS    VOLUME    CAPACITY   ACCESSMODES   AGE

mypvc1    Bound     mypv1     1Gi        RWO           17s

[root@k8s_master ~]# kubectl get pv

NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM            REASON    AGE

mypv1     1Gi        RWO           Recycle         Bound       default/mypvc1             10m

nfs       1Mi        RWX           Retain          Available                              11m

[root@k8s_master ~]# cat nfs-pvc1.yml

kind: PersistentVolumeClaim

apiVersion: v1

metadata:

  name: mypvc1

spec:

  accessModes:

    - ReadWriteOnce

  resources:

    requests:

      storage: 1Gi

[root@k8s_master ~]# cat pod1.yml

kind: Pod

apiVersion: v1

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

[root@k8s_master ~]# kubectl apply -f pod1.yml

pod "mypod1" created

[root@k8s_master ~]# kubectl get pod -o wide

NAME                            READY     STATUS             RESTARTS   AGE       IP                NODE

mypod1                          1/1       Running            0          10s       172.8.18.4        192.168.138.161

[root@k8s_master ~]# kubectl exec mypod1 touch /mydata/houpj

[root@k8s_master ~]# ls /nfsdata/pv1/

Houpj

回收

[root@k8s_master ~]# kubectl delete pvc mypvc1

persistentvolumeclaim "mypvc1" deleted

[root@k8s_master ~]# kubectl get pod -o wide

recycler-for-mypv1              0/1       ContainerCreating   0          11s       <none>            192.168.138.161

[root@k8s_master ~]# kubectl get pv

NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM            REASON    AGE

mypv1     1Gi        RWO           Recycle         Released    default/mypvc1             25m

nfs       1Mi        RWX           Retain          Available                              27m

[root@k8s_master ~]# ls /nfsdata/pv1/

Houpj

[root@k8s_master ~]# kubectl get pv

NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM            REASON    AGE

mypv1     1Gi        RWO           Recycle         Failed      default/mypvc1             30m

nfs       1Mi        RWX           Retain          Available                              32m

[root@k8s_master ~]# kubectl apply -f nfs-pv1.yml

persistentvolume "mypv1" configured

[root@k8s_master ~]# kubectl get pv

NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM     REASON    AGE

mypv1     1Gi        RWO           Retain          Available                       49s

nfs       1Mi        RWX           Retain          Available                       35m

[root@k8s_master ~]# cat nfs-pv1.yml

apiVersion: v1

kind: PersistentVolume

metadata:

  name: mypv1

spec:

  capacity:

    storage: 1Gi

  accessModes:

    - ReadWriteOnce

  persistentVolumeReclaimPolicy: Retain

#  storageClassName: nfs  #不支持

  nfs:

    path: /nfsdata/pv1

    server: 192.168.138.130

#下面的也正確

#apiVersion: v1

#kind: PersistentVolume

#metadata:

#  name: nfs

#spec:

#  capacity:

#    storage: 1Mi

#  accessModes:

#    - ReadWriteMany

#  nfs:

#    server: 192.168.138.130

#    path: "/nfsdata/pv1"

[root@k8s_master ~]# cat nfs-pvc1.yml

kind: PersistentVolumeClaim

apiVersion: v1

metadata:

  name: mypvc1

spec:

  accessModes:

    - ReadWriteOnce

  resources:

    requests:

      storage: 1Gi

 

[root@k8s_master ~]# ls /nfsdata/pv1/

houpj

[root@k8s_master ~]# cd /nfsdata/pv1/

[root@k8s_master pv1]# ls

houpj

[root@k8s_master ~]# kubectl apply -f nfs-pvc1.yml

persistentvolumeclaim "mypvc1" created

[root@k8s_master ~]# kubectl get pod -o wide

NAME                            READY     STATUS             RESTARTS   AGE       IP                NODE

mypod1                          1/1       Running            0          23m       172.8.18.4        192.168.138.161

[root@k8s_master ~]# kubectl exec mypod1 touch /mydata/hello

[root@k8s_master ~]# ls /nfsdata/pv1/

hello

[root@k8s_master ~]# kubectl delete pvc mypvc1

persistentvolumeclaim "mypvc1" deleted

[root@k8s_master ~]# kubectl get pv

NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM            REASON    AGE

mypv1     1Gi        RWO           Retain          Released    default/mypvc1             6m

nfs       1Mi        RWX           Retain          Available                              41m

[root@k8s_master ~]# kubectl get pod -o wide

NAME                            READY     STATUS             RESTARTS   AGE       IP                NODE

mypod1                          1/1       Running            0          25m       172.8.18.4        192.168.138.161

[root@k8s_master ~]# ls /nfsdata/pv1

Hello

雖然 mypv1 中的數據獲得了保留,但其 PV 狀態會一直處於 Released,不能被其餘 PVC 申請。爲了從新使用存儲資源,能夠刪除並從新建立 mypv1。刪除操做只是刪除了 PV 對象,存儲空間中的數據並不會被刪除。

[root@k8s_master ~]# kubectl get pv

NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM            REASON    AGE

mypv1     1Gi        RWO           Retain          Released    default/mypvc1             12m

nfs       1Mi        RWX           Retain          Available                              47m

[root@k8s_master ~]# kubectl delete pv mypv1

persistentvolume "mypv1" deleted

[root@k8s_master ~]# kubectl apply -f nfs-pv1.yml

persistentvolume "mypv1" created

[root@k8s_master ~]# kubectl get pv

NAME      CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM     REASON    AGE

mypv1     1Gi        RWO           Retain          Available                       5s

nfs       1Mi        RWX           Retain          Available                       48m

 

新建的 mypv1 狀態爲 Available,已經能夠被 PVC 申請。

PV 還支持 Delete 的回收策略,會刪除 PV 在 Storage Provider 上對應存儲空間。NFS 的 PV 不支持 Delete,支持 Delete 的 Provider 有 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等

Pv 動態供給沒有條件實驗:https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner

問題:

通常實際中容器化mysql是採用deployment仍是採用statefulset?另外容器化數據庫會採用哪一種類型的存儲多一點?

若是是單個mysql實例,用deployment就能夠了。若是是集羣,能夠用statefulset。statefulset和deploymenty都能用volume,區別主要在於statefulset中的pod都有明確的命名規則,以及啓動順序。存儲的類型不必定,開源ceph較多。

 

[root@k8s_master ~]# cat mysql-pv.yml

apiVersion: v1

kind: PersistentVolume

metadata:

  name: mysql-pv

spec:

  capacity:

    storage: 1Gi

  accessModes:

    - ReadWriteOnce

  persistentVolumeReclaimPolicy: Retain

#  storageClassName: nfs  #不支持

  nfs:

    path: /nfsdata/mysql-pv

    server: 192.168.138.130

[root@k8s_master ~]# cat mysql-pvc.yml

kind: PersistentVolumeClaim

apiVersion: v1

metadata:

  name: mysql-pvc

spec:

  accessModes:

    - ReadWriteOnce

  resources:

    requests:

      storage: 1Gi

[root@k8s_master ~]# kubectl apply -f mysql-pv.yml

persistentvolume "mysql-pv" created

[root@k8s_master ~]# kubectl apply -f mysql-pvc.yml

persistentvolumeclaim "mysql-pvc" created

[root@k8s_master ~]# kubectl get pv,pvc

NAME          CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM               REASON    AGE

pv/mypv1      1Gi        RWO           Retain          Bound       default/mysql-pvc             21m

pv/mysql-pv   1Gi        RWO           Retain          Available                                 45s

pv/nfs        1Mi        RWX           Retain          Available                                 1h

 

NAME            STATUS    VOLUME    CAPACITY   ACCESSMODES   AGE

pvc/mysql-pvc   Bound     mypv1     1Gi        RWO           24s

 

http://blog.51cto.com/goome/2134294?source=dra 格式很重要

[root@master ~]# cat mysql-pv.yml

apiVersion: v1

kind: PersistentVolume

metadata:

  name: mysql-pv

spec:

  capacity:

    storage: 1Gi

  accessModes:

    - ReadWriteOnce

  persistentVolumeReclaimPolicy: Retain

  storageClassName: nfs 

  nfs:

    path: /nfsdata/mysql-pv

    server: 192.168.138.166

[root@master ~]# cat mysql-pvc.yml

kind: PersistentVolumeClaim

apiVersion: v1

metadata:

  name: mysql-pvc

spec:

  accessModes:

    - ReadWriteOnce

  resources:

    requests:

      storage: 1Gi

  storageClassName: nfs

[root@master ~]# cat mysql2.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-data

            mountPath: /var/lib/mysql

        volumes:

          - name: mysql-data

            persistentVolumeClaim:

              claimName: mysql-pv

[root@master ~]# kubectl apply -f mysql2.yml

service "mysql" created

deployment "mysql" created

一直報錯

[root@master ~]# kubectl get pod -o wide

NAME                     READY     STATUS              RESTARTS   AGE       IP        NODE

mysql-774cffbbcd-vw96h   0/1       ContainerCreating   0          11m       <none>    minion1

[root@master ~]# kubectl describe pod mysql-6b5cb74bfc-rbczg

Name:           mysql-6b5cb74bfc-rbczg

Namespace:      default

Node:           minion1/192.168.138.130

Start Time:     Tue, 18 Sep 2018 19:55:10 +0800

Labels:         app=mysql

mount: wrong fs type, bad option, bad superblock on 192.168.138.166:/nfsdata/k8s/mysqlpv1,

       missing codepage or helper program, or other error

       (for several filesystems (e.g. nfs, cifs) you might

       need a /sbin/mount.<type> helper program)

缺乏nfs客戶端

[root@minion1 ~]# yum install nfs-utils

[root@master ~]# kubectl delete -f mysql-svc.yml

service "mysql" deleted

deployment "mysql" deleted

[root@master ~]# kubectl apply -f mysql-svc.yml

service "mysql" created

deployment "mysql" created

[root@master ~]# kubectl get pod -o wide

NAME                     READY     STATUS    RESTARTS   AGE       IP           NODE

mysql-6b5cb74bfc-mpxs5   1/1       Running   0          8s        10.244.1.3   minion1

[root@minion1 ~]# docker ps

CONTAINER ID        IMAGE                                                                           COMMAND                  CREATED             STATUS              PORTS               NAMES

5a2a68004c33        mysql@sha256:4c44f46efaff3ebe7cdc7b35a616c77aa003dc5de4b26c80d0ccae1f9db4a372   "docker-entrypoint..."   About an hour ago   Up About an hour                        k8s_mysql_mysql-6b5cb74bfc-mpxs5_default_73394f6f-bb3a-11e8-8d88-000c29f19fb4_0

[root@minion1 ~]# docker inspect 5a2a68004c33

{

                "Type": "bind",

                "Source": "/var/lib/kubelet/pods/73394f6f-bb3a-11e8-8d88-000c29f19fb4/volumes/kubernetes.io~nfs/mysqlpv1",

                "Destination": "/var/lib/mysql",

                "Mode": "",

                "RW": true,

                "Propagation": "rprivate"

            }

[root@minion1 ~]# docker exec -it 5a2a /bin/bash

root@mysql-6b5cb74bfc-mpxs5:/#  mysql -uroot -ppassword

mysql> create database t1;

Query OK, 1 row affected (0.03 sec)

 

mysql> use t1;create table t1(i int);

Database changed

Query OK, 0 rows affected (0.06 sec)

 

mysql> use t1;insert into t1 values (1),(3),(3);

Database changed

Query OK, 3 rows affected (0.03 sec)

Records: 3  Duplicates: 0  Warnings: 0

 

mysql> use t1;select * from t1;

Database changed

+------+

| i    |

+------+

|    1 |

|    3 |

|    3 |

+------+

3 rows in set (0.00 sec)

[root@master ~]# ls /nfsdata/k8s/mysqlpv1/

auto.cnf  ibdata1  ib_logfile0  ib_logfile1  mysql  performance_schema  t1

模擬故障

[root@minion1 ~]# init 0

很長時間仍然顯示正常,節點不切換;

[root@master ~]# kubectl get pod

NAME                     READY     STATUS    RESTARTS   AGE

mysql-6b5cb74bfc-mpxs5   1/1       Running   0          1h

https://www.sohu.com/a/127037247_515888

[root@master ~]# kubectl get nodes

NAME      STATUS     ROLES     AGE       VERSION

master    Ready      master    16h       v1.9.0

minion1   NotReady   <none>    16h       v1.9.0

[root@master ~]# kubectl get pod

NAME                     READY     STATUS    RESTARTS   AGE

mysql-6b5cb74bfc-mpxs5   1/1       Running   0          14h

[root@master ~]# kubectl get pod -o wide

NAME                     READY     STATUS    RESTARTS   AGE       IP           NODE

mysql-6b5cb74bfc-mpxs5   1/1       Running   0          14h       10.244.1.3   minion1

啓動宕機節點任然應用運行

[root@minion1 ~]# docker ps

CONTAINER ID        IMAGE                                                                           COMMAND                  CREATED             STATUS              PORTS               NAMES

6f27926b39f3        mysql@sha256:4c44f46efaff3ebe7cdc7b35a616c77aa003dc5de4b26c80d0ccae1f9db4a372   "docker-entrypoint..."   38 seconds ago      Up 37 seconds                           k8s_mysql_mysql-6b5cb74bfc-mpxs5_default_73394f6f-bb3a-11e8-8d88-000c29f19fb4_1

數據仍然在

4.13 Secret

應用啓動過程當中可能須要一些敏感信息,好比訪問數據庫的用戶名密碼或者祕鑰。將這些信息直接保存在容器鏡像中顯然不妥,Kubernetes 提供的解決方案是 Secret。

Secret 會以密文的方式存儲數據,避免了直接在配置文件中保存敏感信息。Secret 會以 Volume 的形式被 mount 到 Pod,容器可經過文件的方式使用 Secret 中的敏感數據;此外,容器也能夠環境變量的方式使用這些數據。

[root@master ~]# echo -n admin|base64

YWRtaW4=

[root@master ~]# echo -n 123456 |base64

MTIzNDU2

[root@master ~]# kubectl apply -f secret.yml

secret "mysecret" created

[root@master ~]# cat secret.yml

apiVersion: v1

kind: Secret

metadata:

  name: mysecret

data:

  username: YWRtaW4=

  password: MTIzNDU2

[root@master ~]# kubectl get secret

NAME                  TYPE                                  DATA      AGE

default-token-ps7cs   kubernetes.io/service-account-token   3         19h

mysecret              Opaque                                2         1h

[root@master ~]# kubectl get secret mysecret

NAME       TYPE      DATA      AGE

mysecret   Opaque    2         1h

[root@master ~]# kubectl describe secret mysecret

Name:         mysecret

Namespace:    default

Labels:       <none>

Annotations: 

Type:         Opaque

 

Data

====

password:  6 bytes

username:  5 bytes

[root@master ~]# kubectl edit secret mysecret

apiVersion: v1

data:

  password: MTIzNDU2

  username: YWRtaW4=

kind: Secret

metadata:

  annotations:

    kubectl.kubernetes.io/last-applied-configuration: |

      {"apiVersion":"v1","data":{"password":"MTIzNDU2","username":"YWRtaW4="},"kind":"Secret","metadata":{"annotations":{},"name":"mysecret","namespace":"default"}}

  creationTimestamp: 2018-09-19T03:53:12Z

  name: mysecret

  namespace: default

  resourceVersion: "70131"

  selfLink: /api/v1/namespaces/default/secrets/mysecret

  uid: 87a7dda2-bbbf-11e8-8d88-000c29f19fb4

type: Opaque

[root@master ~]# echo -n YWRtaW4=| base64 --decode

admin[root@master ~]#

[root@master ~]# echo -n MTIzNDU2|base64 --decode

123456[root@master ~]#

[root@master ~]# 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"

      readOnly: true

  volumes:

  - name: foo

    secret:

      secretName: mysecret

[root@master ~]# kubectl apply -f mypod.yml

pod "mypod" created

[root@master ~]# kubectl exec -it mypod sh

/ # ls /etc/foo

password  username

/ # cat /etc/foo/username

admin/ #

/ # cat /etc/foo/password

123456/ # [root@master ~]#

[root@master ~]# cat secret.yml

apiVersion: v1

kind: Secret

metadata:

  name: mysecret

data:

  username: YWRtaW4=

  password: YWJjZGVm

[root@master ~]# kubectl apply -f secret.yml

secret "mysecret" configured

[root@master ~]# kubectl exec -it mypod sh

/ # ls /etc/foo

password  username

/ # cat /etc/foo/username

admin/ #

/ # cat /etc/foo/password

abcdef/ # [root@master ~]#

[root@master ~]# kubectl apply -f mypod.yml

pod "mypod" created

[root@master ~]# 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"

      readOnly: true

  volumes:

  - name: foo

    secret:

      secretName: mysecret

      items:

      - key: username

        path: my-group/my-username

      - key: password

        path: my-group/my-password

[root@master ~]# kubectl exec -it mypod sh

/ #

/ # cd /etc/foo

/etc/foo # ls

my-group

/etc/foo # cd my-group/

/etc/foo/my-group # ls

my-password  my-username

/etc/foo/my-group # cat my-password

abcdef/etc/foo/my-group #

/etc/foo/my-group # cat my-username

admin/etc/foo/my-group #

[root@master ~]# kubectl apply -f mypod.yml

pod "mypod" created

[root@master ~]# 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

    env:

      - name: SECRET_USERNAME

        valueFrom:

          secretKeyRef:

            name: mysecret

            key: username

      - name: SECRET_PASSWORD

        valueFrom:

          secretKeyRef:

            name: mysecret

            key: password

[root@master ~]# kubectl exec -it mypod sh

/ # echo $SECRET_USERNAME

admin

/ # echo $SECRET_PASSWORD

Abcdef

經過環境變量 SECRET_USERNAME 和 SECRET_PASSWORD 成功讀取到 Secret 的數據。

須要注意的是,環境變量讀取 Secret 很方便,但沒法支撐 Secret 動態更新。

4.13.1 ConfigMap

Secret 能夠爲 Pod 提供密碼、Token、私鑰等敏感數據;對於一些非敏感數據,好比應用的配置信息,則能夠用 ConfigMap。

ConfigMap 的建立和使用方式與 Secret 很是相似,主要的不一樣是數據以明文的形式存放。

[root@master ~]# kubectl apply -fconfigmap.yml

configmap "myconfigmap" created

[root@master ~]# kubectl apply -f mypod2.yml

pod "mypod" created

[root@master ~]# kubectl get configmap myconfigmap

NAME          DATA      AGE

myconfigmap   2         3m

[root@master ~]# kubectl describe configmap myconfigmap

Name:         myconfigmap

Namespace:    default

Labels:       <none>

Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","data":{"config1":"xxx","config2":"yyy"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"myconfigmap","namespace":"default"}...

 

Data

====

config1:

----

xxx

config2:

----

yyy

Events:  <none>

[root@master ~]# cat configmap.yml

apiVersion: v1

kind: ConfigMap

metadata:

  name: myconfigmap

data:

  config1: xxx

  config2: yyy

[root@master ~]# kubectl apply -f configmap2.yml

configmap "myconfigmap" created

[root@master ~]# cat configmap2.yml

apiVersion: v1

kind: ConfigMap

metadata:

  name: myconfigmap

data:

  logging.conf: |

    class: logging.handlers.RotatingFileHandler

    formatter: precise

    level: INFO

    filename: %hostname-%timestamp.log

[root@master ~]# kubectl get configmap myconfigmap

NAME          DATA      AGE

myconfigmap   1         30s

[root@master ~]# kubectl describe configmap myconfigmap

Name:         myconfigmap

Namespace:    default

Labels:       <none>

Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","data":{"logging.conf":"class: logging.handlers.RotatingFileHandler\nformatter: precise\nlevel: INFO\nfilename: %hostname-%timestamp...

 

Data

====

logging.conf:

----

class: logging.handlers.RotatingFileHandler

formatter: precise

level: INFO

filename: %hostname-%timestamp.log

 

Events:  <none>

[root@master ~]# cat mypod4.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"

  volumes:

  - name: foo

    configMap:

      name: myconfigmap

      items:

        - key: logging.conf

          path: myapp/logging.conf

[root@master ~]# kubectl apply -f mypod4.yml

pod "mypod" created

[root@master ~]# kubectl get pods

NAME                     READY     STATUS    RESTARTS   AGE

mypod                    1/1       Running   0          15s

mysql-6b5cb74bfc-mpxs5   1/1       Running   1          18h

[root@master ~]# kubectl exec -it mypod sh

/ # cat /etc/myapp/logging.conf

class: logging.handlers.RotatingFileHandler

formatter: precise

level: INFO

filename: %hostname-%timestamp.log

/ #

第5章 Helm

5.1 概念

每一個成功的軟件平臺都有一個優秀的打包系統,好比 Debian、Ubuntu 的 apt,Redhat、Centos 的 yum。而 Helm 則是 Kubernetes 上的包管理器。

本章咱們將討論爲何須要 Helm,它的架構和組件,以及如何使用 Helm。

Why Helm

Helm 到底解決了什麼問題?爲何 Kubernetes 須要 Helm?

答案是:Kubernetes 可以很好地組織和編排容器,但它缺乏一個更高層次的應用打包工具,而 Helm 就是來幹這件事的。

先來看個例子。

好比對於一個 MySQL 服務, Kubernetes 須要部署下面這些對象:

 

咱們能夠將上面這些配置保存到對象各自的文件中,或者集中寫進一個配置文件,而後經過 kubectl apply -f 部署。

到目前爲止,Kubernetes 對服務的部署支持得都挺好,若是應用只由一個或幾個這樣的服務組成,上面的部署方式徹底足夠了。

可是,若是咱們開發的是微服務架構的應用,組成應用的服務可能多達十個甚至幾十上百個,這種組織和管理應用的方式就很差使了:

很難管理、編輯和維護如此多的服務。每一個服務都有若干配置,缺少一個更高層次的工具將這些配置組織起來。

不容易將這些服務做爲一個總體統一發布。部署人員須要首先理解應用都包含哪些服務,而後按照邏輯順序依次執行 kubectl apply。即缺乏一種工具來定義應用與服務,以及服務與服務之間的依賴關係。

不能高效地共享和重用服務。好比兩個應用都要用到 MySQL 服務,但配置的參數不同,這兩個應用只能分別拷貝一套標準的 MySQL 配置文件,修改後經過 kubectl apply 部署。也就是說不支持參數化配置和多環境部署。

不支持應用級別的版本管理。雖然能夠經過 kubectl rollout undo 進行回滾,但這隻能針對單個 Deployment,不支持整個應用的回滾。

不支持對部署的應用狀態進行驗證。好比是否能經過預約義的帳號訪問 MySQL。雖然 Kubernetes 有健康檢查,但那是針對單個容器,咱們須要應用(服務)級別的健康檢查。

Helm 可以解決上面這些問題,Helm 幫助 Kubernetes 成爲微服務架構應用理想的部署平臺。Helm 有兩個重要的概念:chart 和 release。

chart 是建立一個應用的信息集合,包括各類 Kubernetes 對象的配置模板、參數定義、依賴關係、文檔說明等。chart 是應用部署的自包含邏輯單元。能夠將 chart 想象成 apt、yum 中的軟件安裝包。

release 是 chart 的運行實例,表明了一個正在運行的應用。當 chart 被安裝到 Kubernetes 集羣,就生成一個 release。chart 可以屢次安裝到同一個集羣,每次安裝都是一個 release。

Helm 是包管理工具,這裏的包就是指的 chart。Helm 可以:

從零建立新 chart。

與存儲 chart 的倉庫交互,拉取、保存和更新 chart。

在 Kubernetes 集羣中安裝和卸載 release。

更新、回滾和測試 release。

Helm 包含兩個組件:Helm 客戶端 和 Tiller 服務器。

 

 

Helm 客戶端是終端用戶使用的命令行工具,用戶能夠:

 

在本地開發 chart。

管理 chart 倉庫。

與 Tiller 服務器交互。

在遠程 Kubernetes 集羣上安裝 chart。

查看 release 信息。

升級或卸載已有的 release。

Tiller 服務器運行在 Kubernetes 集羣中,它會處理 Helm 客戶端的請求,與 Kubernetes API Server 交互。Tiller 服務器負責:

 

監聽來自 Helm 客戶端的請求。

經過 chart 構建 release。

在 Kubernetes 中安裝 chart,並跟蹤 release 的狀態。

經過 API Server 升級或卸載已有的 release。

簡單的講:Helm 客戶端負責管理 chart;Tiller 服務器負責管理 release。

5.2 部署

https://yq.aliyun.com/articles/159601 tiller 安裝

[root@master ~]# tar xf helm-v2.11.0-linux-amd64.tar.gz -C /tmp/helm-installer/helm/

[root@master linux-amd64]# ls

helm  LICENSE  README.md  tiller

[root@master linux-amd64]# cp helm /usr/local/bin/

[root@master ~]# helm version

Client: &version.Version{SemVer:"v2.11.0", GitCommit:"2e55dbe1fdb5fdb96b75ff144a339489417b146b", GitTreeState:"clean"}

Error: could not find tiller

 

helm init --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.5.1 --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

[root@master ~]# helm version

Client: &version.Version{SemVer:"v2.11.0", GitCommit:"2e55dbe1fdb5fdb96b75ff144a339489417b146b", GitTreeState:"clean"}

Server: &version.Version{SemVer:"v2.5.1", GitCommit:"7cf31e8d9a026287041bae077b09165be247ae66", GitTreeState:"clean"}

[root@master ~]# kubectl get --namespace=kube-system svc tiller-deploy

NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)     AGE

tiller-deploy   ClusterIP   10.108.109.87   <none>        44134/TCP   7m

[root@master ~]# kubectl get --namespace=kube-system deployment tiller-deploy

NAME            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

tiller-deploy   1         1         1            1           8m

 [root@master ~]# kubectl get --namespace=kube-system pod

tiller-deploy-86558fdd5b-4bnd9   1/1       Running   0          11m

 

Helm 安裝成功後,可執行 helm search 查看當前可安裝的 chart。

[root@master ~]# helm search|head -10

NAME                            CHART VERSION   APP VERSION     DESCRIPTION                                                

stable/acs-engine-autoscaler    2.1.3           2.1.1           Scales worker nodes within agent pools                     

stable/aerospike                0.1.7           v3.14.1.2       A Helm chart for Aerospike in Kubernetes                   

stable/anchore-engine           0.1.3           0.1.6           Anchore container analysis and policy evaluation engine s...

stable/artifactory              7.0.3           5.8.4           Universal Repository Manager supporting all major packagi...

stable/artifactory-ha           0.1.0           5.8.4           Universal Repository Manager supporting all major packagi...

stable/aws-cluster-autoscaler   0.3.2                           Scales worker nodes within autoscaling groups.             

stable/bitcoind                 0.1.0           0.15.1          Bitcoin is an innovative payment network and a new kind o...

stable/buildkite                0.2.1           3               Agent for Buildkite                                        

stable/centrifugo               2.0.0           1.7.3           Centrifugo is a real-time messaging server.     

前面說過,Helm 能夠像 apt 和 yum 管理軟件包同樣管理 chart。apt 和 yum 的軟件包存放在倉庫中,一樣的,Helm 也有倉庫。

[root@master ~]# helm repo list

NAME    URL                                                  

stable  https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

local   http://127.0.0.1:8879/charts 

前面說過,Helm 能夠像 apt 和 yum 管理軟件包同樣管理 chart。apt 和 yum 的軟件包存放在倉庫中,一樣的,Helm 也有倉庫。

Helm 安裝時已經默認配置好了兩個倉庫:stable 和 local。stable 是官方倉庫,local 是用戶存放本身開發的 chart 的本地倉庫。

helm search 會顯示 chart 位於哪一個倉庫,好比 local/cool-chart 和 stable/acs-engine-autoscaler。

用戶能夠經過 helm repo add 添加更多的倉庫,好比企業的私有倉庫,倉庫的管理和維護方法請參考官網文檔 https://docs.helm.sh

與 apt 和 yum 同樣,helm 也支持關鍵字搜索:

[root@master ~]# helm search mysql

NAME                            CHART VERSION   APP VERSION DESCRIPTION                                                

stable/mysql                    0.3.5                       Fast, reliable, scalable, and easy to use open-source rel...

stable/percona                  0.3.0                       free, fully compatible, enhanced, open source drop-in rep...

stable/percona-xtradb-cluster   0.0.2           5.7.19     free, fully compatible, enhanced, open source drop-in rep...

stable/gcloud-sqlproxy          0.2.3                       Google Cloud SQL Proxy                                     

stable/mariadb                  2.1.6           10.1.31    Fast, reliable, scalable, and easy to use open-source rel..

包括 DESCRIPTION 在內的全部信息,只要跟關鍵字匹配,都會顯示在結果列表中。

安裝 chart 也很簡單,執行以下命令能夠安裝 MySQL。

helm install stable/mysql

沒有權限報錯

[root@master ~]#  helm install stable/mysql

Error: no available release name found

開啓權限

[root@master ~]# kubectl create serviceaccount --namespace kube-system tiller

serviceaccount "tiller" created

[root@master ~]# kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller

clusterrolebinding "tiller-cluster-rule" created

[root@master ~]# kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

deployment "tiller-deploy" patched

[root@master ~]# helm install stable/mysql

NAME:   wrapping-hog

LAST DEPLOYED: Fri Sep 21 00:29:02 2018

NAMESPACE: default

STATUS: DEPLOYED

 

RESOURCES:

==> v1/Secret

NAME                TYPE    DATA  AGE

wrapping-hog-mysql  Opaque  2     1d

 

==> v1/PersistentVolumeClaim

NAME                STATUS   VOLUME  CAPACITY  ACCESSMODES  STORAGECLASS  AGE

wrapping-hog-mysql  Pending  1d

 

==> v1/Service

NAME                CLUSTER-IP     EXTERNAL-IP  PORT(S)   AGE

wrapping-hog-mysql  10.104.115.37  <none>       3306/TCP  1d

 

==> v1beta1/Deployment

NAME                DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE

wrapping-hog-mysql  1        1        1           0          1d

 

 

NOTES:

MySQL can be accessed via port 3306 on the following DNS name from within your cluster:

wrapping-hog-mysql.default.svc.cluster.local

 

To get your root password run:

 

    MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace default wrapping-hog-mysql -o jsonpath="{.data.mysql-root-password}" | base64 --decode; echo)

 

To connect to your database:

 

1. Run an Ubuntu pod that you can use as a client:

 

    kubectl run -i --tty ubuntu --image=ubuntu:16.04 --restart=Never -- bash -il

 

2. Install the mysql client:

 

    $ apt-get update && apt-get install mysql-client -y

 

3. Connect using the mysql cli, then provide your password:

    $ mysql -h wrapping-hog-mysql -p

 

To connect to your database directly from outside the K8s cluster:

    MYSQL_HOST=127.0.0.1

    MYSQL_PORT=3306

 

    # Execute the following commands to route the connection:

    export POD_NAME=$(kubectl get pods --namespace default -l "app=wrapping-hog-mysql" -o jsonpath="{.items[0].metadata.name}")

    kubectl port-forward $POD_NAME 3306:3306

 

    mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD}

綠色部分

chart 本次部署的描述信息:

NAME 是 release 的名字,由於咱們沒用 -n 參數指定,Helm 隨機生成了一個,這裏是 wrapping-hog。

NAMESPACE 是 release 部署的 namespace,默認是 default,也能夠經過 --namespace 指定。

STATUS 爲 DEPLOYED,表示已經將 chart 部署到集羣

當前 release 包含的資源:Service、Deployment、Secret 和 PersistentVolumeClaim,其名字都是 wrapping-hog-mysql,命名的格式爲 ReleasName-ChartName。

NOTES 部分顯示的是 release 的使用方法。好比如何訪問 Service,如何獲取數據庫密碼,以及如何鏈接數據庫等。

[root@master ~]# kubectl get service

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE

kubernetes           ClusterIP   10.96.0.1       <none>        443/TCP    23h

mysql                ClusterIP   10.108.20.158   <none>        3306/TCP   21h

wrapping-hog-mysql   ClusterIP   10.104.115.37   <none>        3306/TCP   8m

[root@master ~]# kubectl get deployment

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

mysql                1         1         1            1           21h

wrapping-hog-mysql   1         1         1            0           8m

[root@master ~]# kubectl get pod

NAME                                  READY     STATUS     RESTARTS   AGE

mypod                                 1/1       Running    0          3h

mysql-6b5cb74bfc-mpxs5                1/1       Running    1          21h

wrapping-hog-mysql-697f95c9dd-b2t2s   0/1       Init:0/1   0          9m

[root@master ~]# kubectl get pvc

NAME                 STATUS    VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   AGE

mysqlpvc1            Bound     mysqlpv1   1Gi        RWO            nfs1           21h

wrapping-hog-mysql   Pending  

[root@master ~]# helm list

NAME            REVISION    UPDATED                     STATUS      CHART          NAMESPACE

wrapping-hog    1           Fri Sep 21 00:29:02 2018    DEPLOYED    mysql-0.3.5    default 

[root@master ~]# helm delete wrapping-hog

release "wrapping-hog" deleted                                                    9m

chart 是 Helm 的應用打包格式。chart 由一系列文件組成,這些文件描述了 Kubernetes 部署應用時所須要的資源,好比 Service、Deployment、PersistentVolumeClaim、Secret、ConfigMap 等。

單個的 chart 能夠很是簡單,只用於部署一個服務,好比 Memcached;chart 也能夠很複雜,部署整個應用,好比包含 HTTP Servers、 Database、消息中間件、cache 等。

chart 將這些文件放置在預約義的目錄結構中,一般整個 chart 被打成 tar 包,並且標註上版本信息,便於 Helm 部署。

[root@master ~]# ls .helm/cache/archive/

mysql-0.3.5.tgz

[root@master ~]# tree mysql

mysql

├── Chart.yaml

├── README.md

├── templates

│   ├── configmap.yaml

│   ├── deployment.yaml

│   ├── _helpers.tpl

│   ├── NOTES.txt

│   ├── pvc.yaml

│   ├── secrets.yaml

│   └── svc.yaml

└── values.yaml

 

1 directory, 10 files

Chart.yaml

YAML 文件,描述 chart 的概要信息。

name 和 version 是必填項,其餘都是可選。

README.md

Markdown 格式的 README 文件,至關於 chart 的使用文檔,此文件爲可選。

LICENSE

文本文件,描述 chart 的許可信息,此文件爲可選。

requirements.yaml

chart 可能依賴其餘的 chart,這些依賴關係可經過 requirements.yaml 指定,好比:

在安裝過程當中,依賴的 chart 也會被一塊兒安裝。

values.yaml

chart 支持在安裝的時根據參數進行定製化配置,而 values.yaml 則提供了這些配置參數的默認值。

templates 目錄

各種 Kubernetes 資源的配置模板都放置在這裏。Helm 會將 values.yaml 中的參數值注入到模板中生成標準的 YAML 配置文件。

模板是 chart 最重要的部分,也是 Helm 最強大的地方。模板增長了應用部署的靈活性,可以適用不一樣的環境,咱們後面會詳細討論。

templates/NOTES.txt

chart 的簡易使用文檔,chart 安裝成功後會顯示此文檔內容。

與模板同樣,能夠在 NOTE.txt 中插入配置參數,Helm 會動態注入參數值。

5.3 Chart模板

[root@master templates]# cat secrets.yaml

apiVersion: v1

kind: Secret

metadata:

  name: {{ template "mysql.fullname" . }}

  labels:

    app: {{ template "mysql.fullname" . }}

    chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"

    release: "{{ .Release.Name }}"

    heritage: "{{ .Release.Service }}"

type: Opaque

data:

  {{ if .Values.mysqlRootPassword }}

  mysql-root-password:  {{ .Values.mysqlRootPassword | b64enc | quote }}

  {{ else }}

  mysql-root-password: {{ randAlphaNum 10 | b64enc | quote }}

  {{ end }}

  {{ if .Values.mysqlPassword }}

  mysql-password:  {{ .Values.mysqlPassword | b64enc | quote }}

  {{ else }}

  mysql-password: {{ randAlphaNum 10 | b64enc | quote }}

  {{ end }}

從結構看,文件的內容很是像Secret 配置,只是大部分屬性值變成了{{ xxx }}。這些 {{ xxx }}其實是模板的語法。Helm 採用了Go語言的模板來編寫chart。Go模板很是強大,支持變量、對象、函數、流控制等功能。下面咱們經過解析templates/secrets.yaml快速學習模板。

{{ template "mysql.fullname" . }} 定義 Secret 的 name。

關鍵字 template 的做用是引用一個子模板 mysql.fullname。這個子模板是在 templates/_helpers.tpl 文件中定義的

[root@master templates]# cat _helpers.tpl

{{/* vim: set filetype=mustache: */}}

{{/*

Expand the name of the chart.

*/}}

{{- define "mysql.name" -}}

{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}

{{- end -}}

 

{{/*

Create a default fully qualified app name.

We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).

*/}}

{{- define "mysql.fullname" -}}

{{- $name := default .Chart.Name .Values.nameOverride -}}

{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}

{{- end -}}

這個定義仍是很複雜的,由於它用到了模板語言中的對象、函數、流控制等概念。如今看不懂不要緊,這裏咱們學習的重點是:若是存在一些信息多個模板都會用到,則可在 templates/_helpers.tpl 中將其定義爲子模板,而後經過 templates 函數引用。

這裏 mysql.fullname 是由 release 與 chart 兩者名字拼接組成。

根據 chart 的最佳實踐,全部資源的名稱都應該保持一致,對於咱們這個 chart,不管 Secret 仍是 Deployment、PersistentVolumeClaim、Service,它們的名字都是子模板 mysql.fullname 的值。

Chart 和 Release 是 Helm 預約義的對象,每一個對象都有本身的屬性,能夠在模板中使用。若是使用下面命令安裝 chart:

 

helm install stable/mysql -n my

那麼:

{{ .Chart.Name }} 的值爲 mysql。

{{ .Chart.Version }} 的值爲 0.3.0。

{{ .Release.Name }} 的值爲 my。

{{ .Release.Service }} 始終取值爲 Tiller。

{{ template "mysql.fullname" . }} 計算結果爲 my-mysql。

{{ if .Values.mysqlRootPassword }}

  mysql-root-password:  {{ .Values.mysqlRootPassword | b64enc | quote }}

  {{ else }}

  mysql-root-password: {{ randAlphaNum 10 | b64enc | quote }}

  {{ end }}

  {{ if .Values.mysqlPassword }}

  mysql-password:  {{ .Values.mysqlPassword | b64enc | quote }}

  {{ else }}

  mysql-password: {{ randAlphaNum 10 | b64enc | quote }}

  {{ end }}

這裏指定 mysql-root-password 的值,不過使用了 if-else 的流控制,其邏輯爲:

若是 .Values.mysqlRootPassword 有值,則對其進行 base64 編碼;不然隨機生成一個 10 位的字符串並編碼。

Values 也是預約義的對象,表明的是 values.yaml 文件。而 .Values.mysqlRootPassword 則是 values.yaml 中定義的 mysqlRootPassword 參數:

[root@master mysql]# cat values.yaml |head -20

## mysql image version

## ref: https://hub.docker.com/r/library/mysql/tags/

##

image: "mysql"

imageTag: "5.7.14"

 

## Specify password for root user

##

## Default: random 10 character string

# mysqlRootPassword: testing

 

## Create a database user

##

# mysqlUser:

# mysqlPassword:

 

## Allow unauthenticated access, uncomment to enable

##

# mysqlAllowEmptyPassword: true

由於 mysqlRootPassword 被註釋掉了,沒有賦值,因此邏輯判斷會走 else,即隨機生成密碼。

randAlphaNum、b64enc、quote 都是 Go 模板語言支持的函數,函數之間能夠經過管道 | 鏈接。{{ randAlphaNum 10 | b64enc | quote }} 的做用是首先隨機產生一個長度爲 10 的字符串,而後將其 base64 編碼,最後兩邊加上雙引號。

templates/secrets.yaml 這個例子展現了 chart 模板主要的功能,咱們最大的收穫應該是:模板將 chart 參數化了,經過 values.yaml 能夠靈活定製應用

 

5.4 實踐

做爲準備工做,安裝以前須要先清楚 chart 的使用方法。這些信息一般記錄在 values.yaml 和 README.md 中。除了下載源文件查看,執行 helm inspect values 多是更方便的方法。

[root@master ~]# helm inspect values stable/mysql

## mysql image version

## ref: https://hub.docker.com/r/library/mysql/tags/

##

image: "mysql"

imageTag: "5.7.14"

輸出的其實是 values.yaml 的內容。閱讀註釋就能夠知道 MySQL chart 支持哪些參數,安裝以前須要作哪些準備。其中有一部分是關於存儲的:

## Persist data to a persistent volume

persistence:

  enabled: true

  ## database data Persistent Volume Storage Class

  ## If defined, storageClassName: <storageClass>

  ## If set to "-", storageClassName: "", which disables dynamic provisioning

  ## If undefined (the default) or set to null, no storageClassName spec is

  ##   set, choosing the default provisioner.  (gp2 on AWS, standard on

  ##   GKE, AWS & OpenStack)

  ##

  # storageClass: "-"

  accessMode: ReadWriteOnce

  size: 8Gi

chart 定義了一個 PersistentVolumeClaim,申請 8G 的 PersistentVolume。因爲咱們的實驗環境不支持動態供給,因此得預先建立好相應的 PV,其配置文件 mysql-pv.yml 內容爲:

[root@master ~]# cat mysql-pv.yml

apiVersion: v1

kind: PersistentVolume

metadata:

  name: mysql-pv

spec:

  capacity:

    storage: 8Gi

  accessModes:

    - ReadWriteOnce

  persistentVolumeReclaimPolicy: Retain

#  storageClassName: nfs1

  nfs:

    path: /nfsdata/mysql-pv

    server: 192.168.138.166

[root@master ~]# kubectl apply -f mysql-pv.yml

persistentvolume "mysql-pv" created

[root@master ~]# kubectl get pv

NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE

mysql-pv   8Gi        RWO            Retain           Available                                      15s

定製化安裝 chart

除了接受 values.yaml 的默認值,咱們還能夠定製化 chart,好比設置 mysqlRootPassword。

 

Helm 有兩種方式傳遞配置參數:

 

指定本身的 values 文件。

一般的作法是首先經過 helm inspect values mysql > myvalues.yaml生成 values 文件,而後設置 mysqlRootPassword,以後執行 helm install --values=myvalues.yaml mysql。

 

經過 --set 直接傳入參數值,好比

[root@master ~]# helm install stable/mysql --set mysqlRootPassword=abc123 -n my

NAME:   my

LAST DEPLOYED: Fri Sep 21 03:23:31 2018

NAMESPACE: default

STATUS: DEPLOYED

 

RESOURCES:

==> v1/Secret

NAME      TYPE    DATA  AGE

my-mysql  Opaque  2     1d

 

==> v1/PersistentVolumeClaim

NAME      STATUS  VOLUME    CAPACITY  ACCESSMODES  STORAGECLASS  AGE

my-mysql  Bound   mysql-pv  8Gi       RWO          1d

 

==> v1/Service

NAME      CLUSTER-IP      EXTERNAL-IP  PORT(S)   AGE

my-mysql  10.111.229.211  <none>       3306/TCP  1d

 

==> v1beta1/Deployment

NAME      DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE

my-mysql  1        1        1           0          1d

mysqlRootPassword 設置爲 abc123。另外,-n 設置 release 爲 my,各種資源的名稱即爲my-mysql。

經過 helm list 和 helm status 能夠查看 chart 的最新狀態。

[root@master ~]# helm status my

LAST DEPLOYED: Fri Sep 21 03:23:31 2018

NAMESPACE: default

STATUS: DEPLOYED

 

RESOURCES:

==> v1/PersistentVolumeClaim

NAME      STATUS  VOLUME    CAPACITY  ACCESSMODES  STORAGECLASS  AGE

my-mysql  Bound   mysql-pv  8Gi       RWO          1d

 

==> v1/Service

NAME      CLUSTER-IP      EXTERNAL-IP  PORT(S)   AGE

my-mysql  10.111.229.211  <none>       3306/TCP  1d

 

==> v1beta1/Deployment

NAME      DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE

my-mysql  1        1        1           0          1d

 

==> v1/Secret

NAME      TYPE    DATA  AGE

my-mysql  Opaque  2     1d

 

升級和回滾 release

release 發佈後能夠執行 helm upgrade 對其升級,經過 --values 或 --set應用新的配置。好比將當前的 MySQL 版本升級到 5.7.15:

[root@master ~]# helm upgrade --set imageTag=5.7.15 my stable/mysql

Error: UPGRADE FAILED: transport is closing

[root@master ~]# kubectl get deployment my-mysql -o wide

NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE       CONTAINERS   IMAGES         SELECTOR

my-mysql   1         1         1            0           11m       my-mysql     mysql:5.7.14   app=my-mysql

[root@master ~]# helm history my

REVISION    UPDATED                     STATUS      CHART      DESCRIPTION    

1           Fri Sep 21 03:23:31 2018    DEPLOYED    mysql-0.3.5 Install complete

[root@master ~]# helm rollback my 1  #回滾

Kubernetes 給咱們提供了大量官方 chart,不過要部署微服務應用,仍是須要開發本身的 chart,下面就來實踐這個主題。

[root@master ~]# helm create mychart

Creating mychart

[root@master ~]# tree mychart/

mychart/

├── charts

├── Chart.yaml

├── templates

│   ├── deployment.yaml

│   ├── _helpers.tpl

│   ├── ingress.yaml

│   ├── NOTES.txt

│   └── service.yaml

└── values.yaml

 

2 directories, 7 files

開發時建議你們參考官方chart中的模板、values.yaml、Chart.yaml,裏面包含了大量最佳實踐和最經常使用的函數、流控制

調試 chart

只要是程序就會有 bug,chart 也不例外。Helm 提供了 debug 的工具:helm lint 和 helm install --dry-run --debug。

helm lint 會檢測 chart 的語法,報告錯誤以及給出建議。

[root@master ~]# helm lint mychart

==> Linting mychart

[INFO] Chart.yaml: icon is recommended

 

1 chart(s) linted, no failures

故意改錯

[root@master mychart]# vim values.yaml

 

# Default values for mychart.

# This is a YAML-formatted file.

# Declare variables to be passed into your templates.

replicaCount: 1

image:

  repository: nginx

  tag: stable

  pullPolicy IfNotPresent

[root@master ~]# helm lint mychart

==> Linting mychart

[INFO] Chart.yaml: icon is recommended

[ERROR] values.yaml: unable to parse YAML

    error converting YAML to JSON: yaml: line 8: could not find expected ':'

 

Error: 1 chart(s) linted, 1 chart(s) failed

helm install --dry-run --debug 會模擬安裝 chart,並輸出每一個模板生成的YAML內容。

[root@master ~]# helm install --dry-run mychart --debug

[debug] Created tunnel using local port: '35442'

 

[debug] SERVER: "localhost:35442"

 

[debug] Original chart version: ""

[debug] CHART PATH: /root/mychart

 

NAME:   eerie-chinchilla

REVISION: 1

RELEASED: Thu Sep 27 19:57:52 2018

CHART: mychart-0.1.0

USER-SUPPLIED VALUES:

{}

 

COMPUTED VALUES:

image:

  pullPolicy: IfNotPresent

  repository: nginx

  tag: stable

ingress:

  annotations: null

  enabled: false

  hosts:

  - chart-example.local

  tls: null

replicaCount: 1

resources: {}

service:

  externalPort: 80

  internalPort: 80

  name: nginx

  type: ClusterIP

 

HOOKS:

MANIFEST:

 

---

# Source: mychart/templates/service.yaml

apiVersion: v1

kind: Service

metadata:

  name: eerie-chinchilla-mychart

  labels:

    app: mychart

    chart: mychart-0.1.0

    release: eerie-chinchilla

    heritage: Tiller

spec:

  type: ClusterIP

  ports:

    - port: 80

      targetPort: 80

      protocol: TCP

      name: nginx

  selector:

    app: mychart

    release: eerie-chinchilla

---

# Source: mychart/templates/deployment.yaml

apiVersion: extensions/v1beta1

kind: Deployment

metadata:

  name: eerie-chinchilla-mychart

  labels:

    app: mychart

    chart: mychart-0.1.0

    release: eerie-chinchilla

    heritage: Tiller

spec:

  replicas: 1

  template:

    metadata:

      labels:

        app: mychart

        release: eerie-chinchilla

    spec:

      containers:

        - name: mychart

          image: "nginx:stable"

          imagePullPolicy: IfNotPresent

          ports:

            - containerPort: 80

          livenessProbe:

            httpGet:

              path: /

              port: 80

          readinessProbe:

            httpGet:

              path: /

              port: 80

          resources:

            {}

咱們能夠檢視這些輸出,判斷是否與預期相符。

一樣,mychart 目錄做爲參數傳遞給 helm install --dry-run --debug。

5.5 管理和安裝chart

當咱們以爲準備就緒,就能夠安裝 chart,Helm 支持四種安裝方法:

安裝倉庫中的 chart,例如:helm install stable/nginx

經過 tar 包安裝,例如:helm install ./nginx-1.2.3.tgz

經過 chart 本地目錄安裝,例如:helm install ./nginx

經過 URL 安裝,例如:helm install https://example.com/charts/nginx-1.2.3.tgz

[root@master ~]# helm install mychart

NAME:   yodeling-opossum

LAST DEPLOYED: Thu Sep 27 20:04:31 2018

NAMESPACE: default

STATUS: DEPLOYED

 

RESOURCES:

==> v1/Service

NAME                      CLUSTER-IP     EXTERNAL-IP  PORT(S)  AGE

yodeling-opossum-mychart  10.110.28.223  <none>       80/TCP   2s

 

==> v1beta1/Deployment

NAME                      DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE

yodeling-opossum-mychart  1        0        0           0          2s

 

 

NOTES:

1. Get the application URL by running these commands:

  export POD_NAME=$(kubectl get pods --namespace default -l "app=mychart,release=yodeling-opossum" -o jsonpath="{.items[0].metadata.name}")

  echo "Visit http://127.0.0.1:8080 to use your application"

  kubectl port-forward $POD_NAME 8080:80

將 chart 添加到倉庫

chart 經過測試後能夠將其添加到倉庫,團隊其餘成員就可以使用。任何 HTTP Server 均可以用做 chart 倉庫,下面演示在 k8s-node1 上搭建倉庫。

a在 minion1上啓動一個 httpd 容器

[root@minion1 ~]# mkdir /var/www

[root@minion1 ~]# docker run -d -p 8080:80 -v /var/www/:/usr/local/apache2/htdocs/ httpd

b經過helm package將mychart打包。

[root@master ~]# helm package mychart

Successfully packaged chart and saved it to: /root/mychart-0.1.0.tgz

c執行 helm repo index生成倉庫的index文件。

[root@master ~]# helm repo index myrepo/ --url http://192.168.138.130:8080/charts

[root@master ~]# ls myrepo/

index.yaml  mychart-0.1.0.tgz

Helm 會掃描 myrepo 目錄中的全部 tgz 包並生成 index.yaml。--url指定的是新倉庫的訪問路徑。新生成的 index.yaml 記錄了當前倉庫中全部 chart 的信息:

[root@master myrepo]# cat index.yaml

apiVersion: v1

entries:

  mychart:

  - apiVersion: v1

    created: 2018-09-27T20:14:44.409532592+08:00

    description: A Helm chart for Kubernetes

    digest: 08092d2be29635ae9a32858e15ef3abb413b768d13d331d06fde87dd07b6fa21

    name: mychart

    urls:

    - http://192.168.138.130:8080/charts/mychart-0.1.0.tgz

    version: 0.1.0

generated: 2018-09-27T20:14:44.409049277+08:00

d將 mychart-0.1.0.tgz 和 index.yaml 上傳到minion1的/var/www/charts目錄。

e經過 helm repo add 將新倉庫添加到 Helm。

[root@master myrepo]# helm repo add newrepo http://192.168.138.130:8080/charts

"newrepo" has been added to your repositories

[root@master myrepo]# helm repo list

NAME   URL                                                  

stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

local  http://127.0.0.1:8879/charts                         

newrepo http://192.168.138.130:8080/charts                   

[root@master myrepo]# helm search mychart

NAME           VERSION DESCRIPTION               

local/mychart  0.1.0  A Helm chart for Kubernetes

newrepo/mychart 0.1.0  A Helm chart for Kubernetes

倉庫命名爲 newrepo,Helm會從倉庫下載index.yaml。

除了 newrepo/mychart,這裏還有一個 local/mychart。這是由於在執行第 2 步打包操做的同時,mychart 也被同步到了 local 的倉庫。

f已經能夠直接重新倉庫安裝 mychart了

[root@master myrepo]# helm install newrepo/mychart

NAME:   crusty-newt

LAST DEPLOYED: Thu Sep 27 20:36:05 2018

NAMESPACE: default

STATUS: DEPLOYED

 

RESOURCES:

==> v1/Service

NAME                 CLUSTER-IP      EXTERNAL-IP  PORT(S)  AGE

crusty-newt-mychart  10.110.231.167  <none>       80/TCP   0s

 

==> v1beta1/Deployment

NAME                 DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE

crusty-newt-mychart  1        0        0           0          0s

若是之後倉庫添加了新的chart,須要用helm repo update更新本地的index。

[root@master myrepo]# helm repo update

Hang tight while we grab the latest from your chart repositories...

...Skip local chart repository

...Successfully got an update from the "newrepo" chart repository

...Successfully got an update from the "stable" chart repository

Update Complete. ⎈ Happy Helming!⎈

Helm 讓咱們可以像 apt 管理 deb 包那樣安裝、部署、升級和刪除容器化應用。

Helm 由客戶端和 Tiller 服務器組成。客戶端負責管理 chart,服務器負責管理 release。

chart 是 Helm 的應用打包格式,它由一組文件和目錄構成。其中最重要的是模板,模板中定義了 Kubernetes 各種資源的配置信息,Helm 在部署時經過 values.yaml 實例化模板。

Helm 容許用戶開發本身的 chart,併爲用戶提供了調試工具。用戶能夠搭建本身的 chart 倉庫,在團隊中共享 chart。

Helm 幫助用戶在 Kubernetes 上高效地運行和管理微服務架構應用,Helm 很是重要。

 

 

 

第6章 網絡模型

6.1 概念

Kubernetes 做爲編排引擎管理着分佈在不一樣節點上的容器和 Pod。Pod、Service、外部組件之間須要一種可靠的方式找到彼此並進行通訊,Kubernetes 網絡則負責提供這個保障。

 

Kubernetes 網絡模型

Kubernetes 採用的是基於扁平地址空間的網絡模型,集羣中的每一個 Pod 都有本身的 IP 地址,Pod 之間不須要配置 NAT 就能直接通訊。另外,同一個 Pod 中的容器共享 Pod 的 IP,可以經過 localhost 通訊。

這種網絡模型對應用開發者和管理員至關友好,應用能夠很是方便地從傳統網絡遷移到 Kubernetes。每一個 Pod 可被看做是一個個獨立的系統,而 Pod 中的容器則可被看作同一系統中的不一樣進程。

 

Pod 內容器之間的通訊

當 Pod 被調度到某個節點,Pod 中的全部容器都在這個節點上運行,這些容器共享相同的本地文件系統、IPC 和網絡命名空間。

不一樣 Pod 之間不存在端口衝突的問題,由於每一個 Pod 都有本身的 IP 地址。當某個容器使用 localhost 時,意味着使用的是容器所屬 Pod 的地址空間。

好比 Pod A 有兩個容器 container-A1 和 container-A2,container-A1 在端口 1234 上監聽,當 container-A2 鏈接到 localhost:1234,實際上就是在訪問 container-A1。這不會與同一個節點上的 Pod B 衝突,即便 Pod B 中的容器 container-B1 也在監聽 1234 端口。

 

Pod 之間的通訊

Pod 的 IP 是集羣可見的,即集羣中的任何其餘 Pod 和節點均可以經過 IP 直接與 Pod 通訊,這種通訊不須要藉助任何的網絡地址轉換、隧道或代理技術。Pod 內部和外部使用的是同一個 IP,這也意味着標準的命名服務和發現機制,好比 DNS 能夠直接使用。

Pod 與 Service 的通訊

Pod 間能夠直接經過 IP 地址通訊,但前提是 Pod 得知道對方的 IP。在 Kubernetes 集羣中, Pod 可能會頻繁的銷燬和建立,也就是說 Pod 的 IP 不是固定的。爲了解決這個問題,Service 提供了訪問 Pod 的抽象層。不管後端的 Pod 如何變化,Service 都做爲穩定的前端對外提供服務。同時,Service 還提供了高可用和負載均衡功能,Service 負責將請求轉發給正確的 Pod。

 

外部訪問

不管是 Pod 的 IP 仍是 Service 的 Cluster IP,它們只能在 Kubernetes 集羣中可見,對集羣以外的世界,這些 IP 都是私有的。

Kubernetes 提供了兩種方式讓外界可以與 Pod 通訊:

NodePort

Service 經過 Cluster 節點的靜態端口對外提供服務。外部能夠經過 <NodeIP>:<NodePort> 訪問 Service。

 

LoadBalancer

Service 利用 cloud provider 提供的 load balancer 對外提供服務,cloud provider 負責將 load balancer 的流量導向 Service。目前支持的 cloud provider 有 GCP、AWS、Azur 等。

 

爲了保證網絡方案的標準化、擴展性和靈活性,Kubernetes 採用了 Container Networking Interface(CNI)規範。

CNI 是由 CoreOS 提出的容器網絡規範,它使用了插件(Plugin)模型建立容器的網絡棧。

CNI 的優勢是支持多種容器 runtime,不只僅是 Docker。CNI 的插件模型支持不一樣組織和公司開發的第三方插件,這對運維人員來講頗有吸引力,能夠靈活選擇適合的網絡方案。

目前已有多種支持 Kubernetes 的網絡方案,好比 Flannel、Calico、Canal、Weave Net 等。由於它們都實現了 CNI 規範,用戶不管選擇哪一種方案,獲得的網絡模型都同樣,即每一個 Pod 都有獨立的 IP,能夠直接通訊。區別在於不一樣方案的底層實現不一樣,有的採用基於 VxLAN 的 Overlay 實現,有的則是 Underlay,性能上有區別。再有就是是否支持 Network Policy。

6.2 Network Policy

Network Policy 是 Kubernetes 的一種資源。Network Policy 經過 Label 選擇 Pod,並指定其餘 Pod 或外界如何與這些 Pod 通訊。

默認狀況下,全部 Pod 是非隔離的,即任何來源的網絡流量都可以訪問 Pod,沒有任何限制。當爲 Pod 定義了 Network Policy,只有 Policy 容許的流量才能訪問 Pod。

不過,不是全部的 Kubernetes 網絡方案都支持 Network Policy。好比 Flannel 就不支持,Calico 是支持的。咱們接下來將用 Canal 來演示 Network Policy。Canal 這個開源項目頗有意思,它用 Flannel 實現 Kubernetes 集羣網絡,同時又用 Calico 實現 Network Policy。

6.3 部署canal

下載好文件鏡像

 

[root@master ~]# kubectl apply -f rbac.yaml

clusterrole "calico" created

clusterrole "flannel" created

clusterrolebinding "canal-flannel" created

clusterrolebinding "canal-calico" created

[root@master ~]# kubectl apply -f canal.yaml

configmap "canal-config" created

daemonset "canal" created

customresourcedefinition "globalfelixconfigs.crd.projectcalico.org" created

customresourcedefinition "globalbgpconfigs.crd.projectcalico.org" created

customresourcedefinition "ippools.crd.projectcalico.org" created

customresourcedefinition "globalnetworkpolicies.crd.projectcalico.org" created

serviceaccount "canal" created

[root@master ~]#

[root@master ~]#

[root@master ~]#

[root@master ~]# kubectl get --namespace=kube-system daemonset

NAME         DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE

canal        1         1         1         1            1           <none>          3m

kube-proxy   1         1         1         1            1           <none>          24m

[root@master ~]# cat httpd.yml

apiVersion: apps/v1beta1

kind: Deployment

metadata:

  name: httpd

spec:

  replicas: 3

  template:

    metadata:

      labels:

        run: httpd

    spec:

      containers:

      - name: httpd

        image: httpd:latest

        imagePullPolicy: IfNotPresent

        ports:

        - containerPort: 80

 

---

apiVersion: v1

kind: Service

metadata:

  name: httpd-svc

spec:

  type: NodePort

  selector:

    run: httpd

  ports:

  - protocol: TCP

    nodePort: 30000

    port: 8080

targetPort: 80

[root@master ~]# kubectl apply -f httpd.yml

deployment "httpd" created

service "httpd-svc" created

[root@master ~]# kubectl get pod -o wide

NAME                     READY     STATUS                 RESTARTS   AGE       IP            NODE

httpd-78bf969db7-27b2t   0/1       CreateContainerError   0          4s        10.244.1.22   slave1

httpd-78bf969db7-4gx7m   0/1       CreateContainerError   0          4s        <none>        slave2

httpd-78bf969db7-78nxs   0/1       CreateContainerError   0          4s        <none>        slave2

httpd-78bf969db7-tgdvd   0/1       Terminating            3          6m        10.244.1.6    slave1

[root@master ~]# kubectl describe pod httpd-78bf969db7-27b2t

報錯Error: Error response from daemon: mkdir /var/lib/docker/overlay/ed1f3b667e779ee685ad979f581e3bb85643c5ce25a627560af9e65c7c8d8fab-init/merged/dev/pts: cannot allocate memory 內存不夠

從新分配內存

[root@master ~]# kubectl get pod -o wide

NAME                     READY     STATUS    RESTARTS   AGE       IP            NODE

httpd-78bf969db7-27b2t   1/1       Running   1          7m        10.244.1.30   slave1

httpd-78bf969db7-4gx7m   1/1       Running   0          7m        10.244.2.13   slave2

httpd-78bf969db7-78nxs   1/1       Running   1          7m        10.244.2.15   slave2

[root@master ~]# kubectl get svc httpd-svc

NAME        TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE

httpd-svc   NodePort   10.107.139.120   <none>        8080:30000/TCP   12m

[root@master ~]# curl -I 192.168.138.130:30000

HTTP/1.1 200 OK

Date: Sat, 29 Sep 2018 12:56:44 GMT

Server: Apache/2.4.35 (Unix)

Last-Modified: Mon, 11 Jun 2007 18:53:14 GMT

ETag: "2d-432a5e4a73a80"

Accept-Ranges: bytes

Content-Length: 45

Content-Type: text/html

 

[root@master ~]# kubectl run busybox --rm -it --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.

/ # ping 10.244.1.30

PING 10.244.1.30 (10.244.1.30): 56 data bytes

64 bytes from 10.244.1.30: seq=0 ttl=63 time=0.082 ms

64 bytes from 10.244.1.30: seq=1 ttl=63 time=0.075 ms

64 bytes from 10.244.1.30: seq=2 ttl=63 time=0.054 ms

[root@slave1 ~]# curl -I 10.107.139.120:8080

HTTP/1.1 200 OK

Date: Sat, 29 Sep 2018 12:59:34 GMT

Server: Apache/2.4.35 (Unix)

Last-Modified: Mon, 11 Jun 2007 18:53:14 GMT

ETag: "2d-432a5e4a73a80"

Accept-Ranges: bytes

Content-Length: 45

Content-Type: text/html

 

[root@slave1 ~]# ping 10.244.1.30

PING 10.244.1.30 (10.244.1.30) 56(84) bytes of data.

64 bytes from 10.244.1.30: icmp_seq=1 ttl=64 time=0.047 ms

64 bytes from 10.244.1.30: icmp_seq=2 ttl=64 time=0.055 ms

 

 

[root@slave1 ~]# cat policy.yml

kind: NetworkPolicy

apiVersion: networking.k8s.io/v1

metadata:

  name: access-httpd

spec:

  podSelector:

    matchLabels:

      run: httpd

#定義將此 Network Policy 中的訪問規則應用於 label 爲 run: httpd 的 Pod,即 httpd 應用的三個副本 Pod。

  ingress:

  - from:

    - podSelector:

        matchLabels:

          access: "true"

#ingress 中定義只有 label 爲 access: "true" 的 Pod 才能訪問應用。

    ports:

    - protocol: TCP

      port: 80

#只能訪問 80 端口

[root@master ~]# kubectl apply -f policy.yml

networkpolicy "access-httpd" created

[root@master ~]# kubectl get networkpolicy

NAME           POD-SELECTOR   AGE

access-httpd   run=httpd      18s

驗證 Network Policy 的有效性

[root@master ~]# kubectl run houpj --rm -ti --labels="access=true" --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.

/ #

/ #

/ #

/ # wget httpd-svc:8080

Connecting to httpd-svc:8080 (10.107.139.120:8080)

index.html           100% |*****************************************************************************************************************************************************************|    45  0:00:00 ETA

/ # ls

bin         dev         etc         home        index.html  proc        root        sys         tmp         usr         var

/ # ping 10.244.1.30

PING 10.244.1.30 (10.244.1.30): 56 data bytes

^C

--- 10.244.1.30 ping statistics ---

2 packets transmitted, 0 packets received, 100% packet loss

[root@master ~]# kubectl run hou --rm -ti --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.

/ # wget httpd-svc:8080

Connecting to httpd-svc:8080 (10.107.139.120:8080)

^C

[root@master ~]# kubectl run hou --rm -ti --image=busybox /bin/sh

If you don't see a command prompt, try pressing enter.

/ # wget httpd-svc:8080

Connecting to httpd-svc:8080 (10.107.139.120:8080)

^C

[root@master ~]# curl -I 192.168.138.130:30000

^C

[root@master ~]# kubectl get pod -o wide

NAME                       READY     STATUS    RESTARTS   AGE       IP            NODE

busybox-6bdf9b5bbc-4njx5   1/1       Running   0          7m        10.244.1.34   slave1

httpd-78bf969db7-27b2t     1/1       Running   1          1h        10.244.1.30   slave1

httpd-78bf969db7-4gx7m     1/1       Running   0          1h        10.244.2.13   slave2

httpd-78bf969db7-78nxs     1/1       Running   1          1h        10.244.2.15   slave2

[root@master ~]# ping 10.244.1.30

PING 10.244.1.30 (10.244.1.30) 56(84) bytes of data.

^C

--- 10.244.1.30 ping statistics ---

4 packets transmitted, 0 received, 100% packet loss, time 2999ms

集羣節點已經不能訪問 Service, 也 Ping 不到副本 Pod。

集羣外(192.168.56.1)已經不能訪問 Service。

若是但願讓集羣節點和集羣外(192.168.56.1)也可以訪問到應用,能夠對 Network Policy 作以下修改:

無論用;

[root@master ~]# kubectl get svc

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE

httpd-svc    NodePort    10.107.139.120   <none>        8080:30000/TCP   1h

kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP          18h

[root@master ~]# curl 10.107.139.120:8080

^C

[root@master ~]# curl 192.168.138.167:8080

curl: (7) Failed connect to 192.168.138.167:8080; Connection refused

[root@master ~]# curl 192.168.138.168:8080

curl: (7) Failed connect to 192.168.138.168:8080; Connection refused

[root@master ~]# curl -I 192.168.138.130:8080

curl: (7) Failed connect to 192.168.138.130:8080; Connection refused

Kubernetes 採用的是扁平化的網絡模型,每一個 Pod 都有本身的 IP,而且能夠直接通訊。

CNI 規範使得 Kubernetes 能夠靈活選擇多種 Plugin 實現集羣網絡。

Network Policy 則賦予了 Kubernetes 強大的網絡訪問控制機制。

 

參考:cloudman微信公衆號

相關文章
相關標籤/搜索