《kubernetes + .net core 》dev ops部分

目錄

1.kubernetes 預備知識

kubernetes是一個用go語言寫的容器編排框架,常與docker搭配使用。
kubernetes是Google內部的容器編排框架的開源實現。可以用來方便的管理容器集群。具有很多 優點,要了解這些優點,需要先來了解一下kubernetes中的集群資源。這裡指的是kubernetes里的原生的資源,kubernetes也支援自定義的資源。

1.1 集群資源

不區分名稱空間

  • cluster role
  • namespace
  • node
  • persistent volume
  • storage class

1.1.1 role

  • 普通角色 role
  • 集群角色 culster role

群集默認採用RBAC(role base access control)進行集群資源的訪問控制,可以將角色作用於用戶user服務service,限制它們訪問群集資源的許可權範圍。

其中角色又區分為 集群角色 cluster role普通的角色 role ,他們的區別是可以作用的範圍不一致。

普通角色必有名稱空間限制,只能作用於與它同名稱空間的用戶或服務。

集群角色沒有名稱空間限制,可以用於所有名稱空間的用戶或服務。下面的目錄中會詳細介紹。先在這裡提一下

1.1.2 namespace

不指定時默認作用default名稱空間,服務在跨名稱空間訪問其他服務時 域名需要加上名稱空間後輟才能訪問

1.1.3 node

  • 主節點 master
  • 工作節點 none
  • 邊緣節點 none

是一個包含作業系統的機器,作業系統可以是Linux也可以是windwos,可以是實體機也可以是虛擬機,其中的區別下面的其他目錄會詳細說明

1.1.4 persistent volume

持久卷 ,支援的類型很多,包括Google 亞馬遜 阿里云云服務提供商的各種存儲.

由於我們的項目一般是用於區域網內的,所以這裡我著重介紹nfs(network file system)

1.1.5 storage class

存儲類,用於根據pvc 自動創建/自動掛載/自動回收 對應的nfs目錄前

1.2 工作量資源 (消耗cpu ram)

  • pod
  • job
  • cron job
  • replica set
  • deployment
  • daemon set
  • statefull set

1.2.1 pod

工作量的最小單位是pod 其他的類型的工作量都是控制Pod的。

pod相當於docker 中的docker composite,可以由單個或多個容器組成,每個pod有自己的docker網路,pod里的container處於同個區域網中。
其他的控制器都有一個pod template,用於創建Pod

1.2.2 job

工作,一但應用到集群將會創建一個pod做一些工作,具體的工作內容由Pod的實現決定,工作完成後Pod自動終結。

1.2.3 cron job

定時工作任務,一但應用到集群,集群將會定時創建pod 做一些工作,工作完成後pod自動終結

1.2.4 replica set

複製集或稱為副本集,一但應用到集群,會創建相n個相同的 pod,並且會維護這個pod的數量,如果有pod異常終結,replica set會創建一個新的Pod 以維護用戶指定的數量

1.2.5 deplyoment

deplyoment常用來創建無狀態的應用集群。

部署,deplyoment依賴於replicaset ,它支援滾動更新,滾動更新的原理是,在原有的一個replica set的基礎上創建一個新版本的replica set ,

舊版本的replicaset 逐個減少 ,新版本的replicaset逐個新增, 可以設置一個參數指定滾動更新時要保持的最小可用pod數量。

1.2.6 daemon set

守護進程集 ,顧名思義,他的作用就是維護某個作業系統(node)的某個進程(pod)始終工作。當一個dameon set被應用到k8s集群,所有它指定的節點上都會創建某個pod
比如日誌採集器 一個節點上有一個,用daemon set就十分應景。

1.2.7 stateful set

stateful set常用於創建有狀態的服務集群,它具有以下特點

  • 穩定的唯一網路標識符
  • 穩定,持久的存儲
  • 有序,順暢的部署和擴展
  • 有序的自動滾動更新

舉個例子,你有一個容器需要存數據,比如mysql容器,這時你用deplyoment就不合適,因為多個mysql實例各自應該有自己的存儲,DNS名稱。
這個時候就應該使用statefull set。它原理是創建無頭服務(沒有集群ip的服務)和有序號的Pod,並把這個無頭服務的域名+有序號的主機名(pod名稱),獲得唯一的DNS名稱
比如設置stateful set的名稱為web,redplica=2,則會有序的創建兩個Pod:web-0 web-1,當web-0就緒後才會創建web-1,如果是擴容時也是這樣的,而收容的時候順序而是反過來的,會從序號大的Pod開始刪除多餘的Pod

如果把一個名稱為nginx的無頭服務指向這個statufulset,則web-0的dns名稱應該為 web-0.nginx

並且 stateful set會為這兩個Pod創建各自的pvc,由於pod的名稱是唯一的,所以故障重建Pod時,可以把新的Pod關聯到原有的存儲卷上

1.3 存儲和配置資源 (消耗存儲)

  • config map
  • secret map
  • persistent volume claim

1.3.1 config map

ConfigMap 允許你將配置文件與鏡像文件分離,以使容器化的應用程式具有可移植性。

  • 如何創建
  • 如何使用
    創建:
  # 從文件夾創建(文件夾里的文本文件將會被創建成config map
  kubectl create configmap my-config --from-file=path/to/bar

  # 從文件創建
  kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt

  # 從字元串創建
  kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2

  # 從鍵值文本創建 
  kubectl create configmap my-config --from-file=path/to/bar

  # 從env文件創建
  kubectl create configmap my-config --from-env-file=path/to/bar.env

使用:

  • 作為pod的環境變數
  • 作為存儲卷掛載到Pod

1.3.2 secrets

Secret 是一種包含少量敏感資訊例如密碼、令牌或密鑰的對象。 這樣的資訊可能會被放在 Pod 規約中或者鏡像中。 用戶可以創建 Secret,同時系統也創建了一些 Secret。

1.3.3 pvc

  • 由集群管理員管理
  • 由storage class管理

如果由集群管理員管理,由開發人員應向集群管理員申請Pv ,集群管理員要手動的創建Pv,pvc,把pvc給開發人員,開發人員手動掛載pvc到pod

如果由storage class管理,則集群管理員只要創建一個provider, 之後provider會自動監視集群中的Pvc 和pod ,自動創建pv和掛載

2.kubernetes node 組件

kubernetes集群中,至少要有一個主節點,主節點應該有以下組件

  • kubelet
  • kuber-proxy
  • cni網路插件
  • etcd
  • kube-apiserver
  • coreDNS
  • kube-controller-manager
  • kube-schedule

普通節點的組件

  • kubelet

  • kube-proxy

  • cni網路插件

2.1 kubectl

kubernetes節點代理組件,每個node上都需要有,他不是Kubernetes創建的容器,所以在集群中查不到。
他的主要做的工作是

1.向kube api以hostname為名註冊節點
2.監視pod運行參數,使Pod以參數預期的狀態運行,所以Pod有異常通常都能查詢Kubelet的日誌來排查錯誤

2.2 kube-proxy

kubernetes 的服務相關的組件,每個Node上都需要有,除了無頭服務,其他所有服務都由他處理流量

k8s集群在初始化的時候,會指定服務網段和pod網段,其中服務網段的ip都是虛擬Ip

他主要做的工作是:

監視集群的服務,如果服務滿足某些條件,則通過ipvs 把這個服務的流量轉發到各個後端Pod (cluster ip)

2.3 cni網路插件

cni: container network interface

k8s稱之為窗口編排集群,他的核心思想是把不同的容器網路聯合起來,使所有的pod都在一個扁平的網路里,可以互換訪問

為達這個目的就需要容器網路插件,下面介紹一下主流的cni插件,並大致說明一下優劣

  • flannel
  • calico
  • 其他

2.3.1 flannel

Flannel
Flannel

flanel是橋接模式的代表插件,
他的工作原理是用daemonset在每個節點上部署flannel插件,插件設置容器網路並把容器網路資訊通過 kube api存儲到etcd中 。

這樣就確保不會重複註冊網段了,與不同node上的Pod通過 kube-proxy打包 發給其他Node的kuber proxy,kuber proxy再拆包,發給pod
以達到跨node的扁平網路訪問. 這種方式也稱vxlan 或overlay
優點:

  • 網路協議簡單,容易分析。
  • 社區規模比較大,成功案例比較多,資料比較全面,入門比較簡單

缺點:

  • 由於有打包 拆包, 所以通訊效率比較低下
  • 不支援網路策略

2.3.2 calico

calico 是網關模式的代表插件。 它主要由以下幾部分構成
它基於邊界網關協議 BGP(border gateway protocol)
他的工作原理是用daemonset在每個節點上部署calico node, 來構成扁平化容器網路
calico node由以下幾個組件

  • felix
  • confid
  • BIRD(BGP Internet route daemon)

felix 負責編寫路由和訪問控制列表

confid 用於把 felix生成的數據記錄到etcd,用於持久化規則

BIRD 用於廣播felix寫到系統內核的路由規則和訪問控制列表acl和calico的網路

當集群規模比較大的時候還可以可選的安裝 BGP Rotue Reflector(BIRD) 和 Typha

前者用於快速廣播協議,後者用於直接與ETCD通訊,減小 kubeapi的壓力

優點:

  • pod跨node的網路流量 直接進系統內核 走路由表,效率極高
  • 支援網路策略

缺點:

  • 跨node的數據包經過DNAT和SNAT後,分析網路封包會比較複雜
  • 部署也比較複雜

Calico
Calico

2.4 etcd

etc distributed ,一款使用go語言編寫的基於raft共識演算法的分散式KV快取框架 ,
不像redis重性能,而像zookeeper 一樣重數據一致性
特點是有較高的寫入性能
Etcd Disk

Etcd Network

Etcd Cpu

Etcd Memory

Etcd Throughput

Etcd Latency Distribution

2.5 kube-apiserver

k8s 暴露給外部的web api,用於集群的交互 有各種語言的api client開源項目 ,程式設計師也可以在程式中引用,監視一些集群資源

2.6 coreDNS

用於集群中的service 和 pod的域名解析,

也可以配置對集群外的域名應該用哪個DNS解析

2.7 kube-controller-manager

用於 各種控制器(消耗cpu ram)的管理

2.8 kube-schedule

用於 管理控制 Pod調度相關

3.集群的高可用

  • 分散式共識演算法 Raft
  • keepalived
  • haproxy

File

3.1 etcd的raft演算法

raft

raft是etcd的共識演算法,kubernetes用etcd來存儲集群的配置。config map /secret都是基於etcd。

理解raft共識演算法可以知道

  • 為什麼高可用集群主節點是3個 5個 7個 而不是 2個 4個 6個
  • kubernetes的主節點發生單點故障的時候, 存儲的行為會有什麼改變

3.2 keepalived

在高可用環境, keepalived用於虛擬ip的選舉,一旦持有虛擬Ip的節點發生故障,其他的主節點會選擇出新的主節點持有虛擬ip。並且可以配置smtp資訊,當節點故障的時候發郵件通知相關的責任人

onfiguration File for keepalived

global_defs {
   notification_email {
        [email protected]
   }
   notification_email_from [email protected]
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id LVS_1
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    lvs_sync_daemon_inteface eth0
    virtual_router_id 79
    advert_int 1
    priority 100                    #權重 m1 100  m2 90  m3 80
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
      192.168.44.200/24 dev eth0 
    }
}


3.3 haproxy

每個主節點都部署了haproxy代理kube api埠, 所以當它持有虛擬ip的時候,會把所有對kube api的請求負載均衡到所有的主節點上。

global
        chroot  /var/lib/haproxy
        daemon
        group haproxy
        user haproxy
        log 127.0.0.1:514 local0 warning
        pidfile /var/lib/haproxy.pid
        maxconn 20000
        spread-checks 3
        nbproc 8

defaults
        log     global
        mode    tcp
        retries 3
        option redispatch

listen https-apiserver
        bind *:8443
        mode tcp
        balance roundrobin
        timeout server 900s
        timeout connect 15s

        server m1  192.168.44.201:6443 check port 6443 inter 5000 fall 5
        server m2  192.168.44.202:6443 check port 6443 inter 5000 fall 5
        server m3  192.168.44.203:6443 check port 6443 inter 5000 fall 5

4 認證/授權

4.1 authentication

kubernetes集群中的認證對象分為

  1. 用戶
  2. 服務

除此之外,還有一些其他的非kubernetes集群管理的服務會需要訪問集群資源的情況

但是這個暫時不實踐,因為haoyun目前不會使用到這種情況

4.1.1 用戶

用戶不是kuebrnetes 的資源,所以單獨拎出來講。

4.1.1.1 查看用戶

master node在加入集群時,會提示我們手動複製默認的管理員用戶到 $HOME/.kube 文件夾

所以查看 $HOME/.kube/config文件可以知道集群 用戶 認證上下文

[root@www .kube]# cat config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZ...#略 
    server: //www.haoyun.vip:8443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: LS0tLS1CRUdJTiBDR...#略
    client-key-data: LS0tLS1CRUdJTiBS..#.略

4.1.1.2 新增用戶

創建1用戶,並進入用戶文件夾

[root@www .kube]# useradd hbb && cd /home/hbb

創建私鑰

[root@www hbb]# openssl genrsa -out hbb_privateKey.key 2048
Generating RSA private key, 2048 bit long modulus
............................................................+++
.............................................................................................................................................................+++
e is 65537 (0x10001)

創建x.509證書籤名請求 (CSR) ,CN會被識別為用戶名 O會被識別為組

openssl req -new -key hbb_privateKey.key \
-out hbb.csr \
-subj "/CN=hbb/O=hbbGroup1/O=hbbGroup2"
#O可以省略也可以寫多個

為CSR簽入kubernetes 的證書和證書公鑰,生成證書

openssl x509 -req -in hbb.csr \
-CA /etc/kubernetes/pki/ca.crt \
-CAkey /etc/kubernetes/pki/ca.key \
-CAcreateserial \
-out hbb.crt -days 50000 
#證書有效天數 50000天

創建證書目錄 ,存入把公鑰(hbb.crt)和私鑰(hbb_private.key) 放進去

[root@www hbb]# mkdir .certs
[root@www hbb]# cd .certs/
[root@www .certs]# mv ../hbb_privateKey.key ../hbb.crt .
[root@www .certs]# ll
total 8
-rw-r--r--. 1 root root  940 Sep  5 15:41 hbb.csr
-rw-r--r--. 1 root root 1679 Sep  5 15:29 hbb_privateKey.key

創建集群用戶

kubectl config set-credentials hbb \
--client-certificate=/home/hbb/.certs/hbb.crt \
--client-key=/home/hbb/.certs/jean_privatekey.key

創建用戶上下文

kubectl config set-context hbb-context \
--cluster=kubernetes --user=hbb

這時原有的config文件里就多了Hbb這個用戶了

users:
- name: hbb
  user:
    client-certificate: /home/hbb/.certs/hbb.crt
    client-key: /home/hbb/.certs/hbb_privatekey.key

複製 原有的.kube/config文件到hbb用戶文件夾 ,在副本上刪除kubernetes-admin的上下文和用戶資訊。

[root@www .kube]# mkdir /home/hbb/.kube
[root@www .kube]# cp config /home/hbb/.kube/
[root@www .kube]# cd /home/hbb/.kube
[root@www .kube]# vim config
[root@www .kube]# cat config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJU...#略
    server: //www.haoyun.vip:8443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: hbb
  name: hbb-context
current-context: hbb-context
kind: Config
preferences: {}
users:
- name: hbb
  user:
    client-certificate: /home/hbb/.certs/hbb.crt
    client-key: /home/hbb/.certs/hbb_privateKey.key

之後把hbb用戶文件夾授權給hbb用戶

[root@www .kube]# chown -R hbb:  /home/hbb/
#-R 遞歸文件夾 
#hbb: 只設置了用戶沒有設置組

這樣就創建一個用戶了,此時退出root用戶,使用hbb用戶登上去,默認就是使用Hbb的user去訪問集群

但是這時還沒有給hbb授權,所以基本上什麼操作都執行不了,因為沒有許可權

[hbb@www .certs]$ kubectl get pod -A
Error from server (Forbidden): pods is forbidden: User "hbb" cannot list resource "pods" in API group "" at the cluster scope

4.2 authorization (RBAC)

官方文檔

相關的kubernetes 資源

  • namespace
  • roles/clusterRoles
  • rolebindings/clusterRolebindings

4.2.1 namespace

roles和rolebindings 如果要建立關聯,他們必須是同一個名稱空間內。

clusterRoles和clusterRolebindings 沒有名稱空間的限制,它們的規則作用於集群範圍

4.2.2 roles/clusterRoles

在 RBAC API 中,一個角色包含一組相關許可權的規則。許可權是純粹累加的(不存在拒絕某操作的規則)。 角色可以用 Role 來定義到某個命名空間上, 或者用 ClusterRole 來定義到整個集群作用域。

一個 Role 只可以用來對某一命名空間中的資源賦予訪問許可權。 下面的 Role 示例定義到名稱為 “default” 的命名空間,可以用來授予對該命名空間中的 Pods 的讀取許可權:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default #如果是clusterRoles 則沒刪除這一行
  name: pod-reader
rules:
- apiGroups: [""] # "" 指定 API 組
  resources: ["pods","pods/log"] #子資源
  verbs: ["get", "watch", "list"]
  
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["my-configmap"] #具體名稱的資源
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  
#下面這個只有clusterRoles 才可以使用
- nonResourceURLs: ["/healthz", "/healthz/*"] # '*' 在 nonResourceURL 中的意思是後綴全局匹配。
  verbs: ["get", "post"]

clusterRoles比 roles 多出以下的能力

  • 集群範圍資源 (比如 nodes)
  • 非資源端點(比如 “/healthz”)
  • 跨命名空間訪問的有名字空間作用域的資源(如 get pods –all-namespaces)

4.2.3 rolebindings/clusterRolebindings

rolebindings也可以使用ClusterRoles,會將裡面的資源的作用域限定到rolebindings的名稱空間範圍內。

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
  
subjects:
- kind: User #用戶
  name: jane
  apiGroup: rbac.authorization.k8s.io
- kind: Group #用戶組
  name: "frontend-admins"
  apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount #服務帳戶
  name: default
  namespace: kube-system
- kind: Group # 所有myNamespace名稱空間下的服務
  name: system:serviceaccounts:myNamespace
  apiGroup: rbac.authorization.k8s.io
- kind: Group #所有名稱空間的所有服務
  name: system:serviceaccounts
  apiGroup: rbac.authorization.k8s.io
- kind: Group #所有認證過的用戶
  name: system:authenticated
  apiGroup: rbac.authorization.k8s.io
- kind: Group #所有未誰的用戶
  name: system:authenticated
  apiGroup: rbac.authorization.k8s.io
  
  
roleRef:
  kind: Role #可以是集群名角或普通的角色
  name: pod-reader 
  apiGroup: rbac.authorization.k8s.io

5.服務

官方文檔
服務是微服務抽象,常用於通過選擇符訪問一組Pod, 也可以訪問其他對象,

  • 集群外部的服務
  • 其他名稱空間的服務

上面這兩種情況,服務的選擇符可以省略。

5.1 代理模式

kubernetes v1.0時使用用戶空間代理 (userspace)

v1.1添加了iptable代理模式,

v1.2默認使用iptables代理模式

v1.11添加ipvs 模式
當Node上不支援ipvs會回退使用iptables模式

5.1.1 userSpace模式

Userspace

每個結點上部署kube-proxy ,它會監視主結點的apiserver對service 和 endpoints的增刪改

為每個server隨機開一個埠,並寫入集群Ip寫入iptables,把對集群服務的集群Ip的流量 轉發到這個隨機的埠上 ,

然後再轉發到後端的Pod上, 一般是採用輪詢的規則,根據服務上的sessionAnfinity來設置連接的親和性

5.1.2 iptables模式

Iptables

與userspace的區別是 不僅把service寫入Iptables,同時把endpoints也寫入了iptables,
所以不用在內核空間和用戶空間之間來回切換,性能提升

5.1.3 ipvs

Ipvs

ipvs(ip virtrual server)和iptables都是基於netfilter ,但ipvs以哈希表做為基礎數據結構,並工作在內核空間
相比iptables,所以他有更好的性能,也支援更多的負載均衡演算法

  • rr: round-robin 輪詢
  • lc: least connection (smallest number of open connections) 最少連接
  • dh: destination hashing 目標哈希
  • sh: source hashing 源哈希
  • sed: shortest expected delay 最低延遲
  • nq: never queue 不排隊

如果需要粘性會話,可以在服務中設置
service.spec.sessionAffinity 為 clusterip ,默認是none
service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 可以調整會話最長時間,默認是10800秒

5.2 服務發現

服務可以通過環境變數和DNS的方式來發現服務,推薦的做法是通過DNS

5.2.1 通過環境變數

一個名稱為 “redis-master” 的 Service 暴露了 TCP 埠 6379, 同時給它分配了 Cluster IP 地址 10.0.0.11 ,
這個 Service 生成了如下環境變數:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

如果需要在pod中使用這些環境變數,需要在啟動pod之前先啟動服務。

5.2.2 通過DNS

服務直接用服務名稱為域名,
Pod會在服務之前加上ip為域名
例如在名稱空間 hbb下有服務 hbb-api, 服務指向的後端pod Ip地址是10-244-6-27.,則會有dns記錄

# 服務
hscadaexapi.hbb.svc.cluster.local
# pod 
10-244-6-27.hscadaexapi.hbb.svc.cluster.local

dns應該儘可能使用,最好不要使用環境變數的方式

5.3 服務類型

  • clusterip 集群IP
  • nodeport 結點IP
  • loadbalance 外部負載均衡器
  • external ip 外部IP
  • none 無頭服務(有狀態服務)
  • externalname 外部服務

5.3.1 clusterip 集群IP

虛擬的ip ,通常指向一組pod (真實ip)

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
    - name: https
      protocol: TCP
      port: 443
      targetPort: 9377

5.3.2 nodeport 結點IP

每個主結點上的具體埠,通常把 node ip+埠 轉發到 一組pod(真實ip)

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  selector:
    app: MyApp
  ports:
      # 默認情況下,為了方便起見,`targetPort` 被設置為與 `port` 欄位相同的值。
    - port: 80
      targetPort: 80
      # 可選欄位
      # 默認情況下,為了方便起見,Kubernetes 控制平面會從某個範圍內分配一個埠號(默認:30000-32767)
      nodePort: 30007

5.3.3 loadbalance 外部負載均衡器

通常把 外部流量 轉發到 一組pod(真實ip) ,外部ip一般是在雙網卡的邊緣節點上

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  clusterIP: 10.0.171.239
  loadBalancerIP: 78.11.24.19
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
      - ip: 146.148.47.155

5.3.4 external IP

將外部的流量引入服務 ,這種外部Ip 不由集群管理,由由集群管理員維護

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 9376
  externalIPs:
  - 80.11.12.10

5.3.5 none 無頭服務(有狀態服務)

kube-proxy組件不對無頭服務進行代理,無頭服務 加上序號 指向 Pod,固定搭配,

所以即使 服務的pod掛了, 重啟來的服務的 域名也不會換一個,用於有狀態的服務。

後面講到Pod控制器statefulset會再細講

5.3.6 externalname 外部服務

kube-proxy組件不會對外部服務進行代理則是映射到dns 用於描述一個集群外部的服務,有解耦合的作用,

所以它和無頭服務一樣沒有選擇器,他也不由集群管理,而是由集群管理員維護

apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

5.4 集群入口 ingress

由於iptables代理模組或亦 ipvs代理模式都是4層負載均衡,無法對7層協議進行負載均衡,所以對於外部的流量 ,常使用入口資源來進行負載均衡,把外部的流量均衡到服務上

  • ingress contorller
  • ingress 資源

5.4.1 ingress controller

ingress controller 是負載均衡器實例,一個集群中可以部署多個, 每個又可以自為一個負載均衡集群

在創建ingress資源的時候,可以用 註解Annotations:來指定要使用哪個ingress controller

kubernetes.io/ingress.class: nginx

這個nginx是controller容器啟動時 用命令行的方式指定的

    Args:
      /nginx-ingress-controller
      --default-backend-service=kube-system/my-nginx-ingress-default-backend
      --election-id=ingress-controller-leader
      --ingress-class=nginx

5.4.2 ingress 資源

Ingress 公開了從集群外部到集群內服務的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 資源上定義的規則控制。

可以將 Ingress 配置為服務提供外部可訪問的 URL、負載均衡流量、終止 SSL/TLS,以及提供基於名稱的虛擬主機等能力。 Ingress 控制器 通常負責通過負載均衡器來實現 Ingress,儘管它也可以配置邊緣路由器或其他前端來幫助處理流量。

Ingress 不會公開任意埠或協議。 將 HTTP 和 HTTPS 以外的服務公開到 Internet 時,通常使用 Service.Type=NodePort 或 Service.Type=LoadBalancer 類型的服務

5.4.2.1 捕獲重寫Path 轉發

`apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          serviceName: test
          servicePort: 80

5.4.2.2 基於主機域名轉發

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: name-virtual-host-ingress
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          serviceName: service1
          servicePort: 80
  - host: bar.foo.com
    http:
      paths:
      - backend:
          serviceName: service2
          servicePort: 80

6.配置和存儲

  • config map
  • secrets
  • nfs persistent volume
  • empty/hostpath

6.1 config map

ConfigMap 允許你將配置文件與鏡像文件分離,以使容器化的應用程式具有可移植性。

6.1.1 創建config map

  # 從文件夾創建(文件夾里的文本文件將會被創建成config map
  kubectl create configmap my-config --from-file=path/to/bar

  # 從文件創建
  kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt

  # 從字元串創建
  kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2

  # 從鍵值文本創建 
  kubectl create configmap my-config --from-file=path/to/bar

  # 從env文件創建
  kubectl create configmap my-config --from-env-file=path/to/bar.env

6.1.2 使用config map

  • 作為pod的環境變數
  • 作為存儲卷掛載到Pod

6.1.2.1 作為pod的環境變數

創建1個config map 配置文件,在default名稱空間里

kubectl create configmap hbb-config --from-literal=key1=aaa --from-literal=key2=bbb

創建一個pod ,使用busybox鏡像,並把上面的cm 載入到環境變數,在pod 的container裡面加上

  containers:
  - name: busybox
    image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    env:
      - name: HBBKEY1
        valueFrom:
          configMapKeyRef:
            name: hbb-config
            key: key1
      - name: HBBKEY2
        valueFrom:
          configMapKeyRef:
            name: hbb-config
            key: key2

簡易寫法,載入所有hbb-config里的key value

  containers:
  - name: busybox
    image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
      envFrom:
      - configMapRef:
          name: hbb-config

經測試,如果修改了config map ,Pod的環境變數是不會自動更新的,除非刪除pod重新創建

6.1.2.2 作為存儲卷掛載到pod

  containers:
  - name: busybox
    image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
      volumeMounts:
      - name: hbb-cm-volume
        mountPath: /etc/config
  volumes:
    - name: hbb-cm-volume
      configMap:
        name: hbb-config

經驗證,修改config map後,去查看掛載上去的卷,文件中的值也隨之發生了改變,所以這種方式是比較好的方式。

6.2 secrets

Secret 是一種包含少量敏感資訊例如密碼、令牌或密鑰的對象。 這樣的資訊可能會被放在 Pod 規約中或者鏡像中。 用戶可以創建 Secret,同時系統也創建了一些 Secret。

  • 創建secret
  • 驗證 secret
  • 使用 secret

6.2.1 創建secrets

  • 通過文件生成
  • 通過字元串生成
  • 手動創建
  • 通過stringData 應用時加密明文secret
  • 查看驗證

6.2.1.1 通過文件生成

#生成文件
echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txt

#從文件生成
kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt

默認的鍵名就是文件名,如果要另外指定可以–from-file=[key=]source,密碼也是如此

#從文件生成
kubectl create secret generic db-user-pass --from-file=hbb-key=./username.txt --from-file=hbb-pas=./password.txt

6.2.1.2 通過字元串生成

說明:特殊字元(例如 $**=!)可能會被 sell轉義,所以要用”括起來

kubectl create secret generic dev-db-secret \
  --from-literal=username=devuser \
  --from-literal=password='S!B\*d$zDsb='

6.2.1.3 手動創建 secret

加密用戶名admin和密碼password

[root@www ~]# echo -n 'admin' | base64 ; echo -n 'password' |base64
YWRtaW4=
cGFzc3dvcmQ=

創建一個mysecret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

用kubectl 創建

kubectl apply -f ./mysecret.yaml

6.2.1.4 通過stringData 應用時加密明文

創建1個 mysecret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
stringData:
  config.yaml: |-
    apiUrl: "//my.api.com/api/v1"
    username: hbb
    password: hbb-password

執行

kubectl apply -f ./mysecret.yaml

將會創建 一個mysecret 資源,裡面有一個config.yaml的key,它的value是一個加密的字元串。

註: mysecret.yaml第7行的|- 的意思是:將下面三行字元串組合起來,替換右邊的縮進(空格和換行)成一個換行符。

這種方式的好處是,可以和helm一起使用,helm使用go 的模版,可以配置明文的字元

6.2.2 查看驗證secret


[root@www ~]# kubectl describe secret/dev-db-secret
Name:         dev-db-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
password:  12 bytes
username:  7 bytes

使用get 和describe 查看secret都不能直接看到data里的數據。

如果要查看secret加密前的值應該用 get -o yaml 查看到base64字元串後再解碼

[root@www ~]# kubectl get secret/dev-db-secret -o yaml
apiVersion: v1
data:
  password: UyFCXCpkJHpEc2I9
  username: ZGV2dXNlcg==
kind: Secret
...
[root@www ~]# echo UyFCXCpkJHpEc2I9|base64 --decode
S!B\*d$zDsb=
[root@www ~]# echo ZGV2dXNlcg==|base64 --decode
devuser

6.2.3 使用secret

和config map一毛一樣,略

6.3 nfs persistent volume

  • provisioning 供應方式
  • persistent volume 持久卷
  • persistent volume claim 持久卷聲明
  • storage class 存儲類

6.3.1 provisioning 持久卷供應方案

  • 靜態
  • 動態

6.3.1.1 靜態供應

  1. 集群管理員創建Pv
  2. 集群用戶 創建pvc
  3. 集群用戶 綁定pvc到Pod

6.3.1.2 動態供應

  1. 集群管理員創建storage class

  2. storage class監視集群中的 pvc和pod

    • 集群用戶創建pvc時如果掛在這個storage class上,則會自動創建pv,pv繼承storage class的回收策略
    • pod使用了Pvc, 則storage class會自動把pv掛載到pod的卷上
    • pvc 刪除時 根據storage的回收策略回收 pv

6.3.2 pv persistent volume

PersistentVolume(PV)是已經由管理員提供或者動態使用供應的集群中的一塊存儲的存儲類。它是集群中的資源,就像節點是集群資源一樣。PV是類似於Volumes的卷插件,但是其生命周期與使用PV的任何單個Pod無關。

  • access mode 訪問模式
  • reclaim policy 回收策略
  • source 存儲源
  • 節點親和力

6.3.2.1 access mode 訪問模式

  • ReadWriteOnce-可以通過單個節點以讀寫方式安裝該卷RWO
  • ReadOnlyMany-該卷可以被許多節點只讀安裝ROX
  • ReadWriteMany-該卷可以被許多節點讀寫安裝RWX

6.3.2.2 reclaim policy 回收策略

如果用戶刪除了Pod正在使用的PVC,則不會立即刪除該PVC。PVC的清除被推遲,直到任何Pod不再主動使用PVC。另外,如果管理員刪除綁定到PVC的PV,則不會立即刪除該PV。PV的去除被推遲,直到PV不再與PVC結合。

當用戶完成其卷處理後,他們可以從允許回收資源的API中刪除PVC對象。PersistentVolume的回收策略告訴集群在釋放其聲明之後如何處理該卷。當前,可以保留,回收或刪除卷。

  • retain 保留
  • delete 刪除
  • Recycle 回收(已棄用,使用動態供應代替)
6.3.2.2.1 retain 保留

這種策略在pvc刪除後,會保留pv,並釋放pv,但這個pv不能被其他pvc重用。如果要回收需要集群管理員手動的去回收,回收步驟如下

  1. 刪除pv
  2. 手動清理pv中的文件數據
  3. 如果要重用存儲介質,需要重聲明一個pv
6.3.2.2.2 delete 刪除

默認的策略是刪除,刪除Pvc ,如果存儲卷類型支援的話,將會同時刪除pv及其中的文件數據

6.3.2.2.3 recycle 回收(棄用

如果pv的介質支援的話,此回收策略將會使用

rm -rf /volume/*

清理pv的數據,然後使這個pv可以被其他pvc使用,由於這樣經常會導致意外終結的Pod,pv里的數據來不及排查就被回收,所以這種方式已被 棄用。應該使用動態配置+手動回收來避免這種情況發生。

6.3.2.3 存儲源

由於我們集群環境是私有的區域網,所以通常只會使用nfs來作為存儲的介質,其他的類型還有很多,這裡不會介紹更多,需要有需要了解可以到k8s中文社區自行查閱資料,下面是一個由nfs provisioner創建的pv,可以從存儲源看出對應的nfs服務的相關資訊

Source:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    www.haoyun.nfs1
    Path:      /k8s/default-hbb-pvc-pvc-02d94a26-d5df-4e70-9e3f-a12630f2bd41
    ReadOnly:  false

6.3.2.4 節點親和力

pv一旦設置了節點親和力,則與Pv結合的Pod都會部署到命中的節點上

6.3.3 persistent volume claim

PersistentVolumeClaim(PVC)是由用戶進行存儲的請求。它類似於pod。pod消耗節點資源,PVC消耗PV資源。Pod可以請求特定級別的資源(CPU和記憶體)。聲明可以請求特定的大小和訪問模式

靜態供應情況下,pvc綁定Pv

動態供應情況下,pvc綁定 storage class

6.3.4 storage class

雖然PersistentVolumeClaims允許用戶使用抽象存儲資源,但對於不同的pod,用戶通常需要具有不同存儲介質,例如機械硬碟和固態硬碟,機房1和機房2,集群管理員需要能夠提供各種PersistentVolume,這些PersistentVolume不僅在大小和訪問模式上有更多差異,而且還不讓用戶了解如何實現這些卷的細節。

這個是推薦使用的方式,所以這裡重點實踐

  1. nfs 伺服器搭建(www.haoyun.nfs1),並驗證可用性
  2. 基於nfs部署一個 nfs client provisioner(www.haoyun.nfs1/k8s)
  3. 創建1個storage class
  4. 創建1個綁定storage class的pvc
  5. 創建1個綁定pvc的pod
  6. 驗證

6.3.4.1 nfs伺服器搭建

略,

驗證

1. 安裝nfs-client
  # yum install -y nfs-utils

2. 創建掛載目錄
  # mkdir /var/nfs

3. 查看NFS Server目錄
  # showmount -e www.haoyun.nfs1

4. 掛載NFS Server目錄
  # mount -t nfs www.haoyun.nfs1:/k8s ~/nfs-test
  
5. 測試完卸載
   # umount -t nfs ~/nfs-test/

6.3.4.2 nfs client provisioner

6.3.4.3 創建1個storage class

用工具安裝部署完nfs provisioner時已經生成,略

6.3.4.4 創建1個綁定storage class的pvc

創建文件 pvc.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: hbb-pvc
  annotations:
    volume.beta.kubernetes.io/storage-class: "nfs-client"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Mi

添加pvc到集群中的nfs名稱空間,storage class是集群資源,任何名稱空間都可以使用

kubectl apply -f pvc.yaml -n nfs

6.3.4.5 創建pod,驗證nfs provisioner

創建podWithPvc.yaml

kind: Pod
apiVersion: v1
metadata:
  name: podwithpvc
spec:
  containers:
  - name: podwithpvc
    image: busybox:1.28
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "touch /mnt/SUCCESS && exit 0 || exit 1"   #創建一個SUCCESS文件後退出
    volumeMounts:
      - name: nfs-pvc
        mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: hbb-pvc  #與PVC名稱保持一致

添加pod到nfs名稱空間

kubectl apply -f podWithPvc.yaml

這個Pod會掛載storage class生成的pv到/mnt目錄,並創建一個名為seccess的文件

到nfs伺服器上查看/k8s目錄 可以看到多了一個名為storageClass名稱+pod名稱的目錄

[root@www k8s]# ll
total 0
drwxrwxrwx 2 root root 21 Aug 29 09:20 nfs-hbb-pvc-pvc-f1a0750e-2214-43ac-a148-cff3db88d4f4

刪除pod 再刪除pvc 再去查看

[root@www k8s]# ll
total 0
drwxrwxrwx 2 root root 21 Aug 29 09:20 archived-nfs-hbb-pvc-pvc-f1a0750e-2214-43ac-a148-cff3db88d4f4

目錄的名稱變為archived + storageClass名稱 + pod名稱,這是因為storage class 聲明時設置成刪除且歸檔了

[root@www ~]# kubectl describe sc -n nfs nfs-client
Name:                  nfs-client
IsDefaultClass:        No
Annotations:           meta.helm.sh/release-name=hbb-nfs,meta.helm.sh/release-namespace=nfs
Provisioner:           cluster.local/hbb-nfs-nfs-client-provisioner
Parameters:            archiveOnDelete=true
AllowVolumeExpansion:  True
MountOptions:          <none>
ReclaimPolicy:         Delete
VolumeBindingMode:     Immediate
Events:                <none>

這種方式比較直接刪除更能保障數據安全,但是需要集群管理員定時刪除不必要的歸檔,(例如超過1個月的歸檔沒有開發人員認領,就刪除掉)

6.4 empty volume 和 hostpath volume

補充兩種卷,

  • empty
  • hostpath

6.4.1 empty volume

當 Pod 指定到某個節點上時,首先創建的是一個 emptyDir 卷,並且只要 Pod 在該節點上運行,卷就一直存在。 就像它的名稱表示的那樣,卷最初是空的。 儘管 Pod 中的容器掛載 emptyDir 卷的路徑可能相同也可能不同,但是這些容器都可以讀寫 emptyDir 卷中相同的文件。 當 Pod 因為某些原因被從節點上刪除時,emptyDir 卷中的數據也會永久刪除。

說明: 容器崩潰並不會導致 Pod 被從節點上移除,因此容器崩潰時 emptyDir 卷中的數據是安全的。

emptyDir 的一些用途:

  • 快取空間,例如基於磁碟的歸併排序。
  • 為耗時較長的計算任務提供檢查點,以便任務能方便地從崩潰前狀態恢復執行。
  • 在 Web 伺服器容器服務數據時,保存內容管理器容器獲取的文件。

默認情況下, emptyDir 卷存儲在支援該節點所使用的介質上;這里的介質可以是磁碟或 SSD 或網路存儲,這取決於您的環境。 但是,您可以將 emptyDir.medium 欄位設置為 "Memory",以告訴 Kubernetes 為您安裝 tmpfs(基於 RAM 的文件系統)。 雖然 tmpfs 速度非常快,但是要注意它與磁碟不同。 tmpfs 在節點重啟時會被清除,並且您所寫入的所有文件都會計入容器的記憶體消耗,受容器記憶體限制約束。

示例

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}

6.4.2 hostpath volume

不常用的方式, Pod掛載node上的文件目錄到pod中,略

文檔

7.調度

文檔

  • nodeSelector
  • taints/tolerattions
  • affinity/antiAffinity
  • distuption
  • HPA(Horizontal pod Autoscaler)

7.1 nodeSelector(節點選擇器)

  • 節點標籤
  • Pod 加spec.nodeSelector
  • 部署驗證

7.1.1 節點標籤

[root@www ~]# kubectl get no www.haoyun.edge1 --show-labels
NAME               STATUS   ROLES    AGE     VERSION   LABELS
www.haoyun.edge1   Ready    <none>   6d21h   v1.18.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,isEdgeNode=,kubernetes.io/arch=amd64,kubernetes.io/hostname=www.haoyun.edge1,kubernetes.io/os=linux

挑選一個內置的hostname標籤來驗證

kubernetes.io/hostname=www.haoyun.edge1

7.1.2 pod加節點選擇器

創建一個busybox-nodeselector.yaml,在裡面加上一個節點選擇器,

使用了內置的label kubernetes.io/hostname ,限定Pod只能部署到edge1上

[root@www ~]# vim busybox-nodeselector.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox-nodeselector-test
  namespace: default
spec:
  nodeSelector:
    kubernetes.io/hostname: www.haoyun.edge1
  containers:
  - name: busybox
    image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

應用它到集群

kubectl apply -f busybox-nodeselector.yaml

7.1.3 部署驗證

查看運行狀態,確實跑到edge1上了


[root@www ~]# kubectl get pod -A -o wide|grep nodeselec
default       busybox-nodeselector-test                                 1/1     Running       0          22s     10.244.6.61      www.haoyun.edge1   <none>           <none>

查看生成的pod詳情

spec:
  containers:
  - command:
    - sleep
    - "3600"
    image: busybox:1.28
    imagePullPolicy: IfNotPresent
    name: busybox
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-74k2x
      readOnly: true
  dnsPolicy: ClusterFirst
  enableServiceLinks: true
  nodeName: www.haoyun.edge1
  nodeSelector:
    kubernetes.io/hostname: www.haoyun.edge1

驗證通過

7.2 taints/tolerationss (污點/容忍)

污點是打在node上的標記,容忍是打在Pod上的標記

  1. 給node加污點 taints
  2. 給pod打容忍標記 tolerations
  3. 部署pod驗證

7.2.1 給node 打污點標記

查看主節點www.haoyu.m1的污點

kubectl describe no www.haoyun.m1

查看spec.taints


spec:
  podCIDR: 10.244.0.0/24
  podCIDRs:
  - 10.244.0.0/24
  taints:
  - effect: NoSchedule
    key: node-role.kubernetes.io/master

已打一個名為node-role.kubernetes.io/master 的Noschedule的污點

7.2.2 給pod打容忍標記

先用get pod -o wide查看到一個部署在www.haoyun.m1結點上的Pod

然後用describe 或edit查看到它的spec.tolerations

  tolerations:
  - key: CriticalAddonsOnly
    operator: Exists
  - effect: NoSchedule
    key: node-role.kubernetes.io/master
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300

其中的

  - effect: NoSchedule
    key: node-role.kubernetes.io/master

表示可以容忍節點上存在這個污點

7.2.3 部署驗證

7.3 affinity/antiAffinity(親和力/反親和力)

親和力/反親和力 又分為

  • 節點親和力/反親和力
  • pod親和力/反親和力

node親和力用於限定Pod部署到Node上的命中規則,

Pod親和力則用於限定 Pod與pod 布置到同一個Node上

他們都有多種匹配規則和 「軟」 「硬」兩次匹配策略

交叉相其實是四種不同的設置,但由於他們大同小異,故在此只以節點的親和力來做說明。

7.3.1 節點親和力

節點親和力 /反親和力使用步驟如下

  1. 給結點標籤
  2. 給pod加上節點親和力 /反親和力
  3. 部署Pod驗證

7.3.1.1 給結點打標籤

例如給名為www.haoyun.edge1的結點打上 isedgenode的標籤

kubectl label no www.haoyun.edge1 isEdgeNode=true

查看節點已打的標籤

[root@www ~]# kubectl get no www.haoyun.edge1 --show-labels
NAME               STATUS   ROLES    AGE     VERSION   LABELS
www.haoyun.edge1   Ready    <none>   6d20h   v1.18.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,isEdgeNode=,kubernetes.io/arch=amd64,kubernetes.io/hostname=www.haoyun.edge1,kubernetes.io/os=linux

7.3.1.2 給Pod加上節點親和力

查看 nginx-ingress-controller的deployment

kubectl edit deployment -n kube-system

找到pod的template

    template:
      metadata:
        creationTimestamp: null
        labels:
          app: nginx-ingress
          app.kubernetes.io/component: controller
          component: controller
          release: my-nginx-ingress
      spec:
        affinity:
          nodeAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: isEdgeNode
                  operator: Exists

可以看到spec.affinity.nodeAffinity里有一條規則,必需節點選擇器命中isedgenode這個標籤存在, 也就是說打標籤的時候不管是設置什麼值,只要有isedgenode就可以部署上去。

  • requiredDuringSchedulingIgnoredDuringExecution 必需
  • preferredDuringSchedulingIgnoredDuringExecution 儘可能

相比起節點選擇器,他有更靈活的匹配替換,而且可以有「軟」,「硬」兩種策略

寫法相對也複雜得多。

7.3.1.3 部署Pod驗證

由於已經部署過了,所以直接查看,可以看到nginx-ingress-controller的Pod是在www.haoyun.edge1的結點上的。

[root@www ~]# kubectl get pod -A -o wide|grep ingress
kube-system   my-nginx-ingress-controller-77d976f664-dj5vg              1/1     Running       3          6d20h   10.244.6.56      www.haoyun.edge1   <none>           <none>

7.4 disruption 干擾調度

pod不會自動消息,除非自願干擾或 非自願干擾

  • PDB(pod disruption budget)

  • 非自願干擾

  • 自願干擾

7.4.1 pod disruption budget

pdb用於確保pod驅逐的過程中服務的可用Pod數量在安全的範圍內。

官方文檔對不同的部署方案的例子

  • 無狀態前端:

    • 關註:服務容量減少不要超過10%。
      • 解決方案:例如,使用minAvailable 90%的PDB。
  • 單實例有狀態應用程式:

    • 關註:請勿在不與我交談的情況下終止此應用程式。
      • 可能的解決方案1:請勿使用PDB,並且可以承受偶爾的停機時間。
      • 可能的解決方案2:將PDB設置為maxUnavailable = 0。了解(在Kubernetes之外)集群操作員需要在終止之前諮詢您。當集群操作員與您聯繫時,請準備停機,然後刪除PDB以表明已準備好進行中斷。之後重新創建。
  • 多實例有狀態應用程式,例如Consul,ZooKeeper或etcd:

    • 關註:不要將實例數量減少到仲裁以下,否則寫入將失敗。
      • 可能的解決方案1:將maxUnavailable設置為1(適用於不同的應用程式規模)。
      • 可能的解決方案2:將minAvailable設置為法定大小(例如,小數位數為5時為3)。(一次允許更多中斷)。
  • 可重新啟動的批處理作業:

    • 關註:在自願中斷的情況下,工作需要完成。

      • 可能的解決方案:不要創建PDB。作業控制器將創建一個替換容器。

pdb通過設置 maxunAvailable (最大不可用pod個數)或 minAvailable(最小可用pod個數) 來保證Pod驅逐時服務的可用性,例:

例:

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: zookeeper
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: zookeeper

7.4.2 非自願干擾

  • 節點下層物理機的硬體故障
  • 集群管理員錯誤地刪除虛擬機(實例)
  • 雲提供商或虛擬機管理程式中的故障導致的虛擬機消失
  • 內核錯誤
  • 節點由於集群網路隔離從集群中消失
  • 由於節點資源不足導致 pod 被驅逐。

這裡只著重介紹資源不足導致的Pod被驅逐,因為其他情況都與配置無關

7.4.2.1 eviction api

eviction api是一組kubelet的api,用於指定當Node的資源不足時,(一般是指硬碟空間不足或記憶體不足的情況 ,)要如何驅逐Pod以保證node的資源始終在合理的範圍內,可以在kubelet 的config map里配置,或者命令行啟動參數的方式設置eviction api, 可參閱文檔

資源不足

設置Kubelet參數

一個例子:

  • 節點記憶體容量:10Gi
  • 操作員希望為系統守護進程保留 10% 記憶體容量(內核、kubelet等)。
  • 操作員希望在記憶體用量達到 95% 時驅逐 pod,以減少對系統的衝擊並防止系統 OOM 的發生。

為了促成這個場景,kubelet將像下面這樣啟動:

--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi

這個配置的暗示是理解系統保留應該包含被驅逐閾值覆蓋的記憶體數量。

要達到這個容量,要麼某些 pod 使用了超過它們請求的資源,要麼系統使用的記憶體超過 1.5Gi - 500Mi = 1Gi

這個配置將保證在 pod 使用量都不超過它們配置的請求值時,如果可能立即引起記憶體壓力並觸發驅逐時,調度器不會將 pod 放到這個節點上。

7.4.3 自願干擾

  • 程式所有者主動干擾
  • 集群管理員主動干擾

7.4.3.1 程式所有者主動干擾

  • 刪除 Deployment 或其他管理 Pod 的控制器
  • 更新了 Deployment 的 Pod 模板導致 Pod 重啟
  • 直接刪除 Pod(例如,因為誤操作)

7.4.3.2 集群管理員主動干擾

  • 排空節點以進行維修或升級。
  • 從集群中排出節點以縮小集群規模(了解集群自動縮放 )。
  • 從節點上刪除pod,以允許其他東西被調度到該節點。

本節只會介紹排空,因為2 一般用於雲供應商平台,3太簡單略。

7.4.3.2.1 drain /uncordon(排空 )

有時候某個Node需要停止運行維護,比如加記憶體之類的操作,這時如果只是刪除Node上的pod, 則很大概率新的pod會重新被調度到這個node上,這種情況下集群管理員應該排空這個node,在維護結束後再結束之後再使用uncordon,使Pod能被調試到這個維護結束的node上

例如對 www.haoyun.edge1結點做排空操作

kubectl drain www.haoyun.edge1

維護結束之後恢復

kubectl uncordon www.haoyun.edge1

7.5 Horizontal pod Autoscaler

略,需要配合promethues 之類的數據採集才可以使用,可能之後專門講promethues再討論,因為這個功能對浩雲來說不是很重要

8.pod配置

  • enviorment 環境變數
  • volume 卷
  • proms 探針

8.1 設置pod環境變數

  • 寫在docker file里

  • 使用helm 寫pod 參數里傳遞

  • 使用config map

8.2 設置pod的存儲卷

參見 6.配置和存儲

8.3 probes 探針

kubelet 使用存活探測器來知道什麼時候要重啟容器。 例如,存活探測器可以捕捉到死鎖(應用程式在運行,但是無法繼續執行後面的步驟)。 這樣的情況下重啟容器有助於讓應用程式在有問題的情況下更可用。

kubelet 使用就緒探測器可以知道容器什麼時候準備好了並可以開始接受請求流量, 當一個 Pod 內的所有容器都準備好了,才能把這個 Pod 看作就緒了。 這種訊號的一個用途就是控制哪個 Pod 作為 Service 的後端。 在 Pod 還沒有準備好的時候,會從 Service 的負載均衡器中被剔除的。

kubelet 使用啟動探測器可以知道應用程式容器什麼時候啟動了。 如果配置了這類探測器,就可以控制容器在啟動成功後再進行存活性和就緒檢查, 確保這些存活、就緒探測器不會影響應用程式的啟動。 這可以用於對慢啟動容器進行存活性檢測,避免它們在啟動運行之前就被殺掉。

  • 探針探測類型

  • 探針可配置項

  • 啟動探針 startupProbe

  • 就緒探針 readinessProbe

  • 存活探針 livenessProbe

8.3.1 探針探測類型

由於各種探針的寫法是一樣的,只是名稱不同,作用也不同

於是這裡以存活探針為例,實踐以下幾種探針的探測方法

  • 命令行
  • http
  • tcp

8.3.1.1 命令行探針

命令返回成功存活,失敗kubelet根據restartPolicy處置Pod

Pod 的 spec 中包含一個 restartPolicy 欄位,其可能取值包括 Always、OnFailure 和 Never。默認值是 Always。

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5 #首次延遲5秒檢測
      periodSeconds: 5 #每5秒檢測一次

8.3.1.2 http探針

http responed 大於等於200小於400成功 ,否則大於等於400失敗,kubelet根據restartPolicy處置 pod

Pod 的 spec 中包含一個 restartPolicy 欄位,其可能取值包括 Always、OnFailure 和 Never。默認值是 Always。

1.13版本之前,如果設置了 http_proxy環境變數,則探針會使用代理,1.13版本後探針不使用代理。

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/liveness
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

8.3.1.3 tcp探針

tcp探針的示例使用了就緒探針和存探針

並不是就緒探針探測就緒了才會使用存活探針去檢測存活,這兩個探針是並行的

如果需要設置在容器啟動成功後再探測存活,應該使用啟動探針 (startupProbe

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

8.3.2 探針可配置項

Probe 有很多配置欄位,可以使用這些欄位精確的控制存活和就緒檢測的行為:

  • initialDelaySeconds:容器啟動後要等待多少秒後存活和就緒探測器才被初始化,默認是 0 秒,最小值是 0。
  • periodSeconds:執行探測的時間間隔(單位是秒)。默認是 10 秒。最小值是 1。
  • timeoutSeconds:探測的超時後等待多少秒。默認值是 1 秒。最小值是 1。
  • successThreshold:探測器在失敗後,被視為成功的最小連續成功數。默認值是 1。 存活探測的這個值必須是 1。最小值是 1。
  • failureThreshold:當探測失敗時,Kubernetes 的重試次數。 存活探測情況下的放棄就意味著重新啟動容器。 就緒探測情況下的放棄 Pod 會被打上未就緒的標籤。默認值是 3。最小值是 1。

HTTP Probes 可以在 httpGet 上配置額外的欄位:

  • host:連接使用的主機名,默認是 Pod 的 IP。也可以在 HTTP 頭中設置 「Host」 來代替。
  • scheme :用於設置連接主機的方式(HTTP 還是 HTTPS)。默認是 HTTP。
  • path:訪問 HTTP 服務的路徑。
  • httpHeaders:請求中自定義的 HTTP 頭。HTTP 頭欄位允許重複。
  • port:訪問容器的埠號或者埠名。如果數字必須在 1 ~ 65535 之間。

對於 HTTP 探測,kubelet 發送一個 HTTP 請求到指定的路徑和埠來執行檢測。 除非 httpGet 中的 host 欄位設置了,否則 kubelet 默認是給 Pod 的 IP 地址發送探測。 如果 scheme 欄位設置為了 HTTPS,kubelet 會跳過證書驗證發送 HTTPS 請求。 大多數情況下,不需要設置host 欄位。 這裡有個需要設置 host 欄位的場景,假設容器監聽 127.0.0.1,並且 Pod 的 hostNetwork 欄位設置為了 true。那麼 httpGet 中的 host 欄位應該設置為 127.0.0.1。 可能更常見的情況是如果 Pod 依賴虛擬主機,你不應該設置 host 欄位,而是應該在 httpHeaders 中設置 Host

對於一次 TCP 探測,kubelet 在節點上(不是在 Pod 裡面)建立探測連接, 這意味著你不能在 host 參數上配置服務名稱,因為 kubelet 不能解析服務名稱。

9. helm

為了第十章,必須把helm的一直基本姿勢介紹一下,所以有第9章

  • helm是什麼
  • 安裝helm
  • chart 是文件結構介紹
  • helm 語法

9.1 helm是什麼

helm是kubernetes的包管理器,用於查找,分享,使用kubernetes生態的應用。

9.2 安裝helm

  • 前置條件
  • 安裝
    • windows
    • linux

9.2.1 前置條件

  • 擁有一個集群
  • 擁有kubectl,並配置用於與kubeapi通訊的config連接配置文件

一般helm都是安裝在主節點上,開發人員用有許可權範圍的用戶登錄上去操作helm即可,

可以和git結合使用,或安裝helm區域網伺服器Tiller

也可以在開發人員機器上通過config+kubectl 直接連接 上集群 ,但是這樣開發人員需要在自己電腦上安裝kubectl。

9.2.2 安裝

無論是windows還是linux,都是直接去下載helm的二進位文件

linux複製helm二進位到bin目錄:

mv helm /usr/bin

windows設置環境變數:

我的電腦屬性->高級->設置環境變數->path+=

9.3 chart文件結構

xxx_chart/
  Chart.yaml          # chart的資訊文件
  LICENSE             # 可選: 許可證 如:GPL LGPL
  README.md           # 可選: 
  values.yaml         # 默認的配置值文件
  values.schema.json  # 可選: 對value.yaml提供輸入格式校驗的josn文件
  charts/             # 依賴的其他圖表的存放文件夾
  crds/               # 自定義資源
  templates/          # 使用go模版的yaml文件,會從value.yaml或其他文件讀值生成資源yaml
  templates/NOTES.txt # 可選: 安裝成功時,顯示在終端上的文字

9.4 helm語法

helm語法是yaml混合 go模版的語法

聽起來很複雜,其實學起來不複雜

僅需要抓著helm chart安裝生成的文件抽絲剝繭,就能快速掌握,因為本身並不複雜

  • 上下文

    • .

    • .value value.yaml

    • .release 安裝時用戶輸入

    • .chart chart.yaml

  • 模版

    • 定義
    • 使用
  • 其他語法 去官網文檔查

10.把.net core web app部署到集群並附加調試

現有兩個.net core web app

  1. api
  2. blazor

前端將通過ingress暴露給集群外部,後端則只在集群內部。以此為例實踐第10章

  • 工具推薦
    • visual studio
      • visual studio tools for kubernetes
    • visual studio code
      • C#
      • YAML
      • kubernetes
      • cloud code for visual studio code
  • 將現有.net core web app部署到集群

10.1 工具推薦

10.2 將現有.net core web app部署到集群

10.2.1 為現有項目添加chart和docker file

這個在前端和後端web app中都需要做,因為都要部署到集群。

在visual studio 安裝10.1中的擴展後

image-20200906164002575

選擇kubernetes/helm

image-20200906164131015

確定後生成chart文件夾和docker file,還有一個azds.yaml (部署到微軟雲上才用到),

image-20200906164442774

10.2.2 程式配置

10.2.2.1 後端

  • 跨域策略
  • api實現
10.2.2.1.1 埠配置5001
    public class Program
    {
        public static void Main(string[] args)
            => CreateHostBuilder(args).Build().Run();

        public static IHostBuilder CreateHostBuilder(string[] args)
            => Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(
                    webBuilder => webBuilder
                    .UseStartup<Startup>().UseUrls("//*:5001"));
    }

value.yaml中修改服務的targetport

service:
  type: ClusterIP
  port: 80
  targetPort: 5001

修改deployment.yaml里的image 和containerPort

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ template "hscadaexapi.fullname" . }}
  labels:
    # 略
spec:
  revisionHistoryLimit: 0
  replicas: {{ .Values.replicaCount }}
  selector: 
    matchLabels:
      app: {{ template "hscadaexapi.name" . }}
      release: {{ .Release.Name }}
  template:
    # 略
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.service.targetPort}}
              protocol: TCP

10.2.2.1.2 跨域策略

.net core因為安全性設計,默認不支援非同源策略的域名對.net web app進行訪問

下表給出了與 URL //store.company.com/dir/page.html 的源進行對比的示例:

URL 結果 原因
//store.company.com/dir2/other.html 同源 只有路徑不同
//store.company.com/dir/inner/another.html 同源 只有路徑不同
//store.company.com/secure.html 失敗 協議不同
//store.company.com:81/dir/etc.html 失敗 埠不同 ( // 默認埠是80)
//news.company.com/dir/other.html 失敗 主機不同

定義1個策略名稱

        public readonly string myAllowSpecificOrigins = "myAllowSpecificOrigins";

配置策略

       // startup.cs 
        public void ConfigureServices(IServiceCollection services)
        {
            //略
            services.AddCors(o =>
            {
                o.AddPolicy(myAllowSpecificOrigins, build =>
                 {
                     build
                     .AllowAnyOrigin()
                     .AllowAnyHeader()
                     .AllowAnyMethod();
                 });
            });
            //略
        }

添加到中單件管道

		// startup.cs
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //略
            app.UseCors(myAllowSpecificOrigins);
            //略
        }

10.2.2.1.3 api實現

10.2.2.2 前端

  • userApi url
    • 調試時根據launch.json傳變環境變數
    • 部署時通過deployment的pod 模版傳入環境變數
10.2.2.2.1 埠

同10.2.2.1.1,略

10.2.2.2.2 userApi環境變數
10.2.2.2.2.1 調試時

launchSetting.json

{
  "profiles": {
    "HScadaEx.Blazor": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development",
        "userApi": "//localhost:5001"
      },
      "applicationUrl": "//localhost:5011"
    }
  }
}
10.2.2.2.2.2 部署時

value.yaml

# 略
userApi: //hscadaexapi.hbb.svc # //服務名稱.名稱空間.svc
# 略

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ template "hscadaexblazor.fullname" . }}
  # 略
spec:
  #略
  template:
    # 略
    spec:
      containers:
        - name: {{ .Chart.Name }}
          # 略
          env:
            # 略
            - name: userApi
              value: {{ .Values.userApi | quote }}

程式中使用環境變數

		// startup.cs
		public void ConfigureServices(IServiceCollection services)
        {
            var userApi = Environment.GetEnvironmentVariable("userApi");
            services.AddHttpClient("usersApi", x =>
             {
                 x.BaseAddress = new Uri($"{userApi}/api/Users/");
                 x.DefaultRequestHeaders.Add("User-Agent", "BlazorSever");
                 x.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
             }).SetHandlerLifetime(TimeSpan.FromSeconds(30));
            Console.WriteLine($"環境變數userApi = {userApi}");
            services.AddTransient<IBLL.User.IUserService, Service.UsersServer>();

        }

10.2.2.2.3 ingress 集群入口資源

我希望訪問www.haoyun.blazor的時候,可以從集群外部訪問前端應用

訪問鏈路:用戶-> www.haoyun.blazor->dns->集群邊緣節點外部ip->ingress controller(集群)->blazor server–ipvs–>pod(集群)

values.yaml

ingress:
  enabled: true
  annotations:
    # kubernetes.io/tls-acme: "true"
  path: /
  hosts:
    - www.haoyun.blazor
  tls: []
    # - secretName: chart-example-tls
    #   hosts:
    #     - chart-example.local

/template/ingerss.yaml 保持不變即可

{{- if .Values.ingress.enabled -}}
{{- $fullName := include "hscadaexblazor.fullname" . -}}
{{- $servicePort := .Values.service.port -}}
{{- $ingressPath := .Values.ingress.path -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: {{ $fullName }}
  labels:
    app: {{ template "hscadaexblazor.name" . }}
    chart: {{ template "hscadaexblazor.chart" . }}
    release: {{ .Release.Name }}
    heritage: {{ .Release.Service }}
{{- with .Values.ingress.annotations }}
  annotations:
{{ toYaml . | indent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
  tls:
  {{- range .Values.ingress.tls }}
    - hosts:
      {{- range .hosts }}
        - {{ . }}
      {{- end }}
      secretName: {{ .secretName }}
  {{- end }}
{{- end }}
  rules:
  {{- range .Values.ingress.hosts }}
    - host: {{ . }}
      http:
        paths:
          - path: {{ $ingressPath }}
            backend:
              serviceName: {{ $fullName }}
              servicePort: http
  {{- end }}
{{- end }}

10.2.3 理解/修改docker file以支援調試

默認的docker file解讀

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base #以..3.1環境為基礎創建一個名為base的鏡像
WORKDIR /app #設置工作目錄 保存為匿名鏡像
EXPOSE 80 #導出埠 80  保存為匿名鏡像

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build #以..sdk3.1為基礎創建一個名為build的鏡像
WORKDIR /src #設置工作目錄 保存為匿名鏡像
COPY ["src/test/test.csproj", "src/test/"] #複製csobj文件 保存為匿名鏡像

RUN dotnet restore "src/test/test.csproj" #還原nuget包 保存為匿名鏡像
COPY . . #遞歸複製解決方案到 src目錄 保存為匿名鏡像
WORKDIR "/src/src/test" #設置工作目錄為鏡像中的test項目目錄 保存為匿名鏡像
RUN dotnet build "test.csproj" -c Release -o /app/build #在鏡像中編譯 保存為匿名鏡像

FROM build AS publish #以build 為基礎 創建一個名為publish的鏡像
RUN dotnet publish "test.csproj" -c Release -o /app/publish #發布到/app/publish目錄 保存為匿名鏡像

FROM base AS final #base為基礎創建一個名為final的鏡像
WORKDIR /app #設置工作目錄,保存為匿名鏡像
COPY --from=publish /app/publish . #複製publish鏡像的/app/publish文件夾到final的工作目錄 保存為匿名鏡像
ENTRYPOINT ["dotnet", "test.dll"] # 設置入口 保存為匿名鏡像

修改release->debug ,添加調試工具後

以api 的dockerfile為例, blazor略

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 5001 #導出所需埠
RUN apt-get update   && apt-get install -y --no-install-recommends unzip   && apt-get install -y procps && rm -rf /var/lib/apt/lists/*  && curl -sSL //aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg # 調試工具安裝

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["src/HScadaEx.API/HScadaEx.API.csproj", "src/HScadaEx.API/"]
COPY ["src/HScadaEx.IBLL/HScadaEx.IBLL.csproj", "src/HScadaEx.IBLL/"]
COPY ["src/HScada.Model/HScada.Model.csproj", "src/HScada.Model/"]
COPY ["src/HscadaEx.BLL/HscadaEx.BLL.csproj", "src/HscadaEx.BLL/"]
COPY ["src/HScadaEx.Core/HScadaEx.Core.csproj", "src/HScadaEx.Core/"]
RUN dotnet restore "src/HScadaEx.API/HScadaEx.API.csproj"
COPY . .
WORKDIR "/src/src/HScadaEx.API"
RUN dotnet build "HScadaEx.API.csproj" -c Debug -o /app/build #改為debug,不需要調試就用release

FROM build AS publish
RUN dotnet publish "HScadaEx.API.csproj" -c Debug -o /app/publish#改為debug,不需要調試就用release

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "HScadaEx.API.dll"]

10.2.4 部署

  1. 構建docker ,把docker鏡像傳輸到工作節點
  2. 複製chart到 主節點
  3. 使用helm 部署到 hbb名稱空間

10.2.4.1 構建docker鏡像

由於個人電腦資源有限,所以我這裡只使用docker savedocker load命令 將docker鏡像複製到僅有的一個工作節點上。正確的做法是在集群外部搭建一個docker私有倉庫,按需拉取。

已知私有倉庫部署方式

  • docker-registry 簡易的,沒有認證授權功能
  • Harbor
  • nexus

對於 2和3 有認證授權的鏡像

正確的姿勢是配置好證書後,導入為secret資源,在拉取鏡像時指定secret資源即可。

10.2.4.2 複製chart到主節點

同樣 chart也有私有伺服器可以搭建,名為Tiller,正確的姿勢也是應該搭伺服器,讓集群調度的時候按需獲取,

10.2.4.3 helm 常用指令

更多資訊查看官網文檔helm -h

查看

helm ls -n 名稱空間

添加repository

helm add repo 名稱 url

安裝

helm install -n 名稱空間 release名稱 chart
#其中的chart 可以是本地文件,也可以是tiller上的chart路徑

更新

helm upgrade -n 名稱空間 release名稱  chart

查看變更歷史記錄

helm history -n 名稱空間 release名稱

回滾

helm rollback

刪除

helm delete -n 名稱空間 release名稱

10.2.3 調試

  • 通過kubernetes api 的客戶端進行附加調試
  • 開發過程中的快速調試okteto

10.2.3.1 通過kubernetes api客戶端

10.2.3.1.1 安裝kubectl 配置 集群連接資訊
  • 下載kubectl二進位,添加到系統變數path

  • 複製集群管理員生成的 config文件到 c:\用戶\.kube 文件夾

10.2.3.1.2 修改docker file
  • release->debug
  • 安裝vsdbg
#See //aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 5001
EXPOSE 5002
RUN apt-get update   && apt-get install -y --no-install-recommends unzip   && apt-get install -y procps && rm -rf /var/lib/apt/lists/*  && curl -sSL //aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["src/HScadaEx.API/HScadaEx.API.csproj", "src/HScadaEx.API/"]
COPY ["src/HScadaEx.IBLL/HScadaEx.IBLL.csproj", "src/HScadaEx.IBLL/"]
COPY ["src/HScada.Model/HScada.Model.csproj", "src/HScada.Model/"]
COPY ["src/HscadaEx.BLL/HscadaEx.BLL.csproj", "src/HscadaEx.BLL/"]
COPY ["src/HScadaEx.Core/HScadaEx.Core.csproj", "src/HScadaEx.Core/"]
RUN dotnet restore "src/HScadaEx.API/HScadaEx.API.csproj"
COPY . .
WORKDIR "/src/src/HScadaEx.API"
RUN dotnet build "HScadaEx.API.csproj" -c Debug -o /app/build

FROM build AS publish
RUN dotnet publish "HScadaEx.API.csproj" -c Debug -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "HScadaEx.API.dll"]
10.2.3.1.3 LaunchSetting.json

這裡用cloud code插件生成調試配置

launchSetting.json

{

    "version": "0.2.0",
    "configurations": [       

        {
            "name": "Attach to Kubernetes Pod (.NET Core)",
            "type": "cloudcode.kubernetes",
            "request": "attach",
            "language": "NETCore",
            "podSelector": {
                "app": "hscadaexblazor" //pod的app label
            },
            "localRoot": "${workspaceFolder}", //本地工作區
            "remoteRoot": "/app" //遠程容器中的工作目錄
        }

    ]
}

.net core目前只能用於附加調試, go/node.js/py 可以熱重載調試,從Google插件的文檔上看到的。

這種方式的缺點很明顯,如果要調試過程修改了程式碼,由於不能熱重載,只能在本地改好程式碼->重生成docker image->推送到docker repository ->更新image到集群 ->再附加調試

所以比較推薦另一種方式的調試,雖然也不支援熱重載,但是用dotnet watch run,在程式碼重生成時實時響應到容器中,並重啟容器中的程式

10.2.3.2 通過okteto 在開發過程中快速調試

okteto是一個開源的項目,用於簡化各種技術棧 在kubernetes中的開發工程。

傳統的kubernetes 服務開發過程就是不斷的重複這個過程

while(調試ing)
{
    附加調試->修改程式碼->生成docker鏡像->推送鏡像->拉鏡像->更新到集群
}

okteto的方式是把開發環境打包成一個鏡像,實時同步容器與本地的文件變化,轉發本地流量到容器 、轉發容器流量到本地 等連接調試用的是ssh ,配合dotnet watch run 可以僅是生成程式碼就把生成的結果應用到容器中,雖然不是真正的熱重載,容器中的程式會重啟,但快速了很多,而且這是各種技術棧都能用的,不僅限於.net 的萬金油,於是流程簡化為

do 
{
    dotnet watch run
}while(調試ing)
{
    附加調試->修改程式碼->生成
}
10.2.3.2.1 安裝okteto
  • 下載二進位文件,並加到系統環境變數path
  • 複製集群管理員生成的config ,放到 c:\用戶\.kube文件夾
10.2.3.2.2 在解決方案中添加okteto.yaml

更多參考官方文檔

name: hscadaexapi #deployment service 的名稱
namespace: hbb #名稱空間
image: mcr.microsoft.com/dotnet/core/sdk #開發環境變數
environment:
- ASPNETCORE_ENVIRONMENT=Development #環境變數
command:
- bash #啟動命令
workdir: /okteto #工作目錄
remote: 22000 # ssh調試埠  把本地22000->容器22
sync:  #同步的文件夾   本地:容器
- .:/okteto
forward:  #埠轉發    本地:容器
- 5001:5001
persistentVolume: {}

10.2.3.2.3 啟動okteto ,
okteto up

這裡集群里會多出一個deployment 和service 資源, 跑起一個pod,這個pod是空的,僅僅是一個開發環境,

然後okteto會並 當前文件夾的文件同步到遠程容器中的$workdir ,並執行 $command

接下去就是在容器中啟動.net core程式

dotnet watch run

查看工作台輸出 已經啟動了程式的話, 在本地訪問 localhost:forward 應該訪問到容器上了

10.2.3.2.4 通過ssh連接容器調試

大多數ide都支援ssh遠程調試,這裡我以visual studioi為例

alt+shift+p 附加調試,選擇 ssh ,配置參數

image-20200912202749683

之後就會讓你選擇要附加的進程了

image-20200912202913252

Tags: