手動搭建K8S集群


閱讀本文前默認您已經了解k8s相關知識,適用於想快速部署進行開發

1.環境准備

1.1安裝虛擬機

准備三台以上Linux服務器(虛擬機) 我這里使用centos7.6作為鏡像文件創建三台虛擬機

配置要求:2G以上\30G硬盤\2顆cpu核心

image.png

1.2系統初始化

以下操作沒有特殊說明默認在每台服務器上都執行命令

關閉防火牆

systemctl stop firewalld
systemctl disable firewalld

關閉 selinux

sed -i 's/enforcing/disabled/' /etc/selinux/config
setenforce 0

關閉 swap

swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab

根據規划設置主機名 這里針對不同虛擬機設置不同名稱

hostnamectl set-hostname <hostname>

master

image.png

node1

image.png

node2

image.png

在 master節點 添加 其它兩個節點hosts

cat >> /etc/hosts << EOF
192.168.182.128 k8smaster
192.168.182.129 k8snode1
192.168.182.130 k8snode2
EOF

設置網絡

cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

讓配置生效

sysctl --system

同步服務器時間

yum install ntpdate -y
ntpdate time.windows.com

1.3安裝依賴環境

每台服務器安裝Docker

wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
yum -y install docker-ce-18.06.1.ce-3.el7
systemctl enable docker && systemctl start docker

修改docker源

cat > /etc/docker/daemon.json << EOF
{
  "registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
EOF

修改k8s的阿里yum源

cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

安裝kubeadm,kubelet和kubectl

yum install -y kubelet-1.18.0 kubeadm-1.18.0 kubectl-1.18.0
systemctl enable kubelet

2.部署k8s節點

在master節點啟動相關組件 注意把對應ip改成你的master節點的ip(192.168.182.128)

kubeadm init --apiserver-advertise-address=192.168.182.128 --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.18.0 --service-cidr=10.96.0.0/12 --pod-network-cidr=10.244.0.0/16

執行完畢查看結果 會看到successfully!下邊有一段腳本

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user: mkdir -pHOME/.kube sudo cp -i /etc/kubernetes/admin.confHOM**E/.kubesudoc**pi/etc/kubernete**s/admin.con**fHOME/.kube/config sudo chown(id -u)😦i**du):(id -g) $HOME/.kube/config You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 192.168.182.128:6443 --token 5qvw4s.b7fd0vl7gg9gc600
--discovery-token-ca-cert-hash sha256:b18f63757d14f9d26b853b8e94bda10d4751c988c58c4f9da5a4e7dd7df1d141

以下操作基於上邊安裝成功的信息來操作(執行的時候為你安裝成功的腳本信息)

我們復制上邊的腳本在master節點執行

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

然后看上邊最后一段腳本 在node節點 使用kubeadm join命令把node加入到master節點

kubeadm join 192.168.182.128:6443 --token 5qvw4s.b7fd0vl7gg9gc600 \
    --discovery-token-ca-cert-hash sha256:b18f63757d14f9d26b853b8e94bda10d4751c988c58c4f9da5a4e7dd7df1d141 

注意 默認token有效期為24小時,當過期之后,該token就不可用了。這時就需要重新創建token,操作如下:

kubeadm token create --print-join-command

部署CNI網絡插件

下載這個yml文件之后修改下名字

wget https://blog.ddddddddd.top/upload/2021/06/kube-flannel-8395a7b9f4e24e3a874dab894e425dc6.yaml
mv kube-flannel-8395a7b9f4e24e3a874dab894e425dc6.yaml kube-flannel.yaml
kubectl apply -f kube-flannel.yaml

查看集群狀態 使用kubectl命令查看節點狀態

kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8smaster Ready master 64m v1.18.0
k8snode1 Ready 57m v1.18.0
k8snode2 Ready 56m v1.18.0

3.集群測試

使用deployment創建一個nginx應用

kubectl create deployment nginx --image=nginx

查看應用的狀態

kubectl get po

image.png

使用expose對外暴露訪問端口

kubectl expose deployment nginx --port=80 --type=NodePort

查看暴露的端口號

kubectl get pod,svc

image.png

在瀏覽器中隨便找一個集群的NodeIP進行訪問 192.168.182.128:31741 192.168.182.129:31741 192.168.182.130:31741

image.png

image.png

4.Yml文件

上邊我們已經把集群搭建起來並使用命令創建了一個可以對外提供的服務,但是如果我們要創建一些比較復雜的大型應用,這種創建方式是不可取的.總不能每次創建應用都要手敲一大串命令來執行把~~

我們在啟動java程序的時候或者跑一個批處理任務的時候,你是不是要寫很多變量參數供程序來讀取並執行,k8s中的大部分資源都可以通過yml文件來配置.

4.1簡單的yml文件什么樣?

人類的創造力是最神奇的,但是人類的模仿能力也是很強大的! 我們可以先看別人寫的yml文件是什么樣子,然后再模仿去寫,久而久之...

我先列一下yml中一些必要的字段解釋

參數名 字段類型 說明
version String K8s API的版本,使用kubectl api-versions 可以查詢到,目前基本都是基於v1
kind String 指當前的yml定義的資源類型和角色,例如:pod、service
metadata Object 元數據對象,固定值寫metadata
metadata.name String 元數據對象的名字,例如Pod的名字
metadata.namespace String 元數據對象的命名空間,用來隔離資源,例如default、自定義xxx
Spec Object 詳細定義對象,固定值寫Spec
Spec.container[] list 這里是spec對象的容器列表定義
Spec.container[].name String 定義容器的名字
Spec.container[].image String 鏡像的名稱

上邊這些只是最基本的參數,k8s中的參數有超級多!用到的時候可以去搜索引擎上邊搜索對應的含義

4.2如何使用yml文件?

我們在搭建集群的時候使用 kubectl create 創建了一個nginx的服務, 這個命令其實就是幫我們寫了一個yml,並且執行了.

我們使用命令再創建一個nginx的服務但是不讓運行,導出它的yml看一下

kubectl create deployment app-nginx --image=nginx -o yaml --dry-run > nginx.yaml

可以根據我們上邊列出的yml字段和下邊的對照一下

[root@k8smaster ~]# cat nginx.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: app-nginx
  name: app-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: app-nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

然后我們就可以基於這個yml文件修改一些東西並使用apply命令來創建這個資源

kubectl apply -f nginx.yaml

可以看到我們又創建一個app-nginx的服務 image.png

5.Pod詳解

Pod 是 k8s 系統中可以創建和管理的最小單元, 是資源對象模型中由用戶創建或部署的最

小資源對象模型, 也是在 k8s 上運行容器化應用的資源對象, 其他的資源對象都是用來支 撐或者擴展 Pod 對象功能的, 比如控制器對象是用來管控 Pod 對象的, Service 或者 Ingress 資源對象是用來暴露 Pod 引用對象的, PersistentVolume 資源對象是用來為 Pod 提供存儲等等, k8s 不會直接處理容器, 而是 Pod, Pod 是由一個或多個 container 組成 Pod 是 Kubernetes 的最重要概念, 每一個 Pod 都有一個特殊的被稱為” 根容器“的 Pause 容器。 Pause 容器對應的鏡 像屬於 Kubernetes 平台的一部分, 除了 Pause 容器, 每個 Pod 還包含一個或多個緊密相關的用戶業務容器

太長不看,我們都知道容器可以通過docker來創建,創建出來的容器是單進程的,pod就是一組容器的集合,是多進程了.通俗理解就是k8s不管理單獨的容器,它管理的是一組容器,也就是一個pod,一般情況下,我們一個pod就定義一個容器.所以也可以理解成k8s管理pod就是管理一個容器一個服務.我們看張圖就直觀了

image.png

5.1 Pod是什么?

我們假設現在有個應用,里邊用了mysql、redis(非傳統部署) 那我們用k8s怎么來啟動這樣的應用呢, 我們定義pod資源,里邊有三個容器,第一個是我們應用自身的程序容器,第二個是mysql的容器,第三個是redis的容器.這樣一組容器的集合就是一個POD,我們在啟動應用的時候,就會啟動是三個服務三個進程,我們只需要針對Pod來進行管理就行了~一般我們一個pod里邊只定義一個容器即可.

5.2 Pod的生命周期

我們在前邊創建nginx的時候也看到了pod的狀態為Running,其實它有很多狀態

狀態 說明
Pending API Server已經創建了該Pod,但Pod中的一個或多個容器的鏡像還沒有穿件,准備過程
Running Pod內所有的容器已創建,且至少一個容器處於運行狀態、正在啟動狀態。正在重啟狀態
Completed Pod內所有的容器均成功執行退出,且不會再重啟
Failed Pod內所有容器均已退出,但至少一個容器退出失敗
Unknown 由於某種原因無法獲取Pod狀態,例如網絡通信不佳

5.3 Pod的重啟策略

以上邊的nginx配置為例 我們設置restartPolicy的策略

    spec:
      containers:
      - image: nginx
        name: nginx
      restartPolicy: Never
重啟策略 說明
Always 當容器失效時,由kubelet自動重啟該容器
OnFailure 當容器終止運行且退出嗎不為0時,由kubelet自動重啟該容器
Never 不論容器運行狀態如何,kubelet都不會重啟該容器

5.4 Pod的鏡像拉取策略

還是來看我們之前創建nginx的deployment時候的yml文件 在containers這個屬性下邊 我們可以設置imagesPullPolicay的策略

    spec:
      containers:
      - image: nginx
        name: nginx
	imagePullPolicy: Always
        resources: {}
鏡像拉取策略 說明
IfNotPresent 默認值, 鏡像在宿主機上不粗糙你在的時候才會拉取
Always 每次創建Pod都會重新拉取一次鏡像
Never Pod 永遠不會主動拉取這個鏡像

5.4針對Pod的資源配置

每個 Pod 都可以對其能使用的服務器上的計算資源設置限額, Kubernetes 中可以設置限額

的計算資源有 CPU 與 Memory 兩種, 其中 CPU 的資源單位為 CPU 數量,是一個絕對值而非相 對值。 Memory 配額也是一個絕對值, 它的單 位是內存字節數。 Kubernetes 里, 一個計算資源進行配額限定需要設定以下兩個參數: Requests 該資源最 小申請數量, 系統必須滿足要求 Limits 該資源最大允許使用的量, 不能突破, 當容器試 圖使用超過這個量的資源時, 可能會被 Kubernetes Kill 並重啟

說人話就是對pod進行一些資源的設置(cpu 內存),在k8s調度節點的時候按照條件來尋找 還是以剛剛的nginx配置文件來實例

    spec:
      containers:
      - image: nginx
        name: nginx
        resources: 
	  requests:
	    memory: "64Mi"
	    cpu: "250m"
	  limits:
	    memory: "128Mi"
	    cpu: "500m"

按照上邊在resources屬性中設置 requests和limits的值 這里的cpu單位是絕對值, 250m代碼使用0.25核 這里的配置指明,k8s在調度的時候,這個pod最低要使用0.25核和64M內存的資源, 下邊的是最大限制

5.5 Pod的健康檢查

我們在前邊知道了pod的幾種狀態. 我們詳細看下Running狀態的說明 |Pod內所有的容器已創建,且至少一個容器處於運行狀態、正在啟動狀態。正在重啟狀態| 假設我們的容器已經創建,並且正在啟動,但是數據庫連接失敗了.這個時候我們看pod狀態是正常的 但是以應用層面來說,這個應用是無法對外提供服務的.所以我們得有一種策略從應用層面檢查應用是否正常 一般生產環境我們可以通過檢查httpGet方式,向服務發送http請求,看響應碼是否正常來決定服務是否正常

我們還是以nginx的yml來配置說明

    spec:
      containers:
      - image: nginx
        name: nginx
      livenessProbe:
	httpGet:
	  path: /index
	initialDelaySeconds: 5
	periodSecondes: 5
	failureThreshold: 3
策略 說明
livenessProbe 如果檢查失敗,就殺死容器,根據pod的restartPolicay來操作
readinessProbe 如果檢查失敗,K8s會把pod從service endpoints中剔除

Probe支持三種檢查方法

方法 說明
httpGet 發送HTTP請求,返回200-400范圍狀態碼為成功
exec 執行shell命令返回狀態碼是0為成功
tcpSocket 發起TCP Socket建立成功

每種方式通用的參數含義

參數 說明
initialDelaySeconds 容器啟動后開始探測之前需要等待多少秒,如果應用啟動一般30s的話,就設置為30s
periodSeconds 執行探測的頻率(多少秒執行一次),默認為10s,最小值為1
successThreshold 探針失敗后,最少連續多少次才視為成功,默認值為1。最小值為1
failureThreshold 最少連續多少次失敗才視為失敗。默認值為3,最小值為1

5.6 Pod的調度策略

我們在前邊理解了Pod的資源配置,我們可以配置Pod的最小調度cpu和內存, 其實這個就算是影響Pod的調度策略了.它只會去尋找滿足條件的Node來進行部署。

5.6.1 節點選擇器

我們以nginx配置為例 可以看到下邊我們配置了兩個nginx的yml 我們用nodeSelector標簽配置了(env:xxx)這個值, 代表將我們的pod調度到具有env標簽的node機器 第一個調度到具有env標簽並且值為dev的Node節點上 第二個調度到具有env標簽並且值為prod的Node節點上

    spec:
      nodeSelector:
	env:dev
      containers:
      - image: nginx
        name: nginx
    spec:
      nodeSelector:
	env:prod
      containers:
      - image: nginx
        name: nginx

那Node的節點又是如何設置的呢? 我們可以使用label命令來為node打標簽如下

kubectl label node k8snode1 env=dev
kubectl label node k8snode2 env=prod

kubectl get nodes k8snode1  --show-labels
kubectl get nodes k8snode2  --show-labels

這樣我們就分別為兩台Node節點打上了不同的標簽,通過命令查看node節點具有的標簽

5.6.2 節點親和性

節點親和性也和node的標簽關聯,但是和上邊的節點選擇器不太一樣 依然是用nginx的配置文件來說明 這個nodeAffinity標簽的屬性着實有點多,但是我們只需要關注里邊的matchExpressions標簽的值即可, 先說一下兩種親和性的特點

親和性 說明
requiredDuringSchedulingIgnoredDuringExcution 硬親和性,如果沒有滿足matchExpressions條件,那就一直調度尋找
preferredDuringSchedulingIgnoreDuringExcution 軟親和性,如果沒有滿足matchExpressions條件,那別的節點也可以

結合下邊的配置文件來看

    spec:
      arrinity:
	nodeAffinity:
	  requiredDuringSchedulingIgnoredDuringExcution:
	    nodeSelectorTerms:
	    - matchExpressions:
	      - key: env
	        operator: In
		values:
		- dev
		- dev1
      containers:
      - image: nginx
        name: nginx

尋找帶有標簽env並且值為dev或者dev1的來調度, 沒有的話Pod的狀態就一直為Pending

    spec:
      arrinity:
	nodeAffinity:
	  preferredDuringSchedulingIgnoreDuringExcution:
	  - weight:1
	    preferencd:
	      matchExpressions:
	      - key: group
		operator: In
		values:
		- public
      containers:
      - image: nginx
        name: nginx

尋找帶有標簽group並且值為public的來調度, 找一遍沒有話,再根據其它調度規則來調度。

常用的operator操作符有 In、NotIn、Exists、Gt、Lt、DonseNotExists

5.6.3 污點策略

上邊我們說的幾種方法都是基於Pod來進行配置的,還有一種就是設置Node的污點屬性主動來管理Pod的調度規則

我們先看一下master節點和非master節點的污點屬性

[root@k8smaster ~]# kubectl describe node k8smaster | grep Taint
Taints:             node-role.kubernetes.io/master:NoSchedule
[root@k8smaster ~]# kubectl describe node k8snode1 | grep Taint
Taints:             <none>
[root@k8smaster ~]# kubectl describe node k8snode2 | grep Taint
Taints:             <none>

可以看到master節點的Taint屬性有個 node-role.kubernetes.io/master:NoSchedule 值 先說以下污點的三個類型

污點類型 說明
NoSchedule 一定不會調度
PreferNoSchdule 盡量不被調度
NoExecute 不會被調度,並且驅逐Node已有的Pod

所以master節點有NoSchedule屬性 pod一般不會調度到master節點,為什么說一般不會呢,后邊再說

如何給Node打污點

使用taint命令給node1節點打上NoSchedule類型的污點

kubectl taint node k8snode1 group=public:NoSchedule

污點容忍 上邊我們NoSchedule類型污點一定不會被調度,那我為什么說master節點一般不會被調度呢 我們還是以nginx配置文件來說明 以剛剛node1節點為例

    spec:
      tolerations:
      - key: "group"
        operator: "Equal"
	value: "public"
	effect: "NoSchedule"
      containers:
      - image: nginx
        name: nginx

上邊的tolerations代表 我們可以調度到污點類型為NoSchedule key為group value為public的,操作符同上邊的也有很多種

關於Pod基本策略我們就先了解到這里, 我們在創建nginx的時候用到了kubectl create deployment 命令,那這個deployment是什么呢?我們下邊來看看

6.Controller

我們知道k8s號稱容器編排工具,具備容災、動態擴縮容等等實現自動化運維的功能,那么我們在前邊已經認識到了Pod的概念,Controller就是更高層面的管理運行容器的對象. controller有很多種,我們一般現在用的就是deployment來部署應用

6.1 使用deployment部署應用

我們還是以nginx來為例,使用deployment進行部署應用

創建yml模板文件

kubectl create deployment nginx --image=nginx --dry-run -o yaml > nginx.yaml

我們來對這個yml分析一下 kind代表資源類型:Deployment metadata中的name屬性代表資源的名稱:nginx

spec中就是一些調度的屬性了 replicas是指運行幾個副本,簡單理解一個副本就對應一個Pod,我們在這里修改為3個 deployment與pod之間是通過標簽來關聯的。 也就是selector.matchLabels.app 這個屬性 與containers[n].name 這個屬性關聯, 這里都為nginx

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

修改之后我們使用命令創建這個副本

kubectl apply -f nginx.yaml

查看狀態

[root@k8smaster k8s]# kubectl get deploy
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   3/3     3            3           99s
[root@k8smaster k8s]# kubectl get po
NAME                    READY   STATUS    RESTARTS   AGE
nginx-f89759699-gz8dd   1/1     Running   0          101s
nginx-f89759699-l9n6z   1/1     Running   0          101s
nginx-f89759699-zt547   1/1     Running   0          101s

我們創建完副本之后,這些pod只能內部訪問 我們要通過service來對外暴露端口 我們創建一個service的yml文件看一下

kubectl expose deployment nginx --port=80 --type=NodePort --target-port=80 --name=nginx --dry-run -o yaml > nginx-svc.yaml

其實yml里邊的內容就是我們剛剛輸入的命令參數 kind代表資源類型,是Service metadata定義Service的名字 spec定義一些規則 port代表內部通信的端口 protocol代表傳輸協議 targetPort代表容器內的端口,nginx暴漏的是80端口 selector.app 與pod標簽一致 type代表以NodePort方式暴漏,有很多種 具體的屬性可以參考文末的參考鏈接

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: NodePort
status:
  loadBalancer: {}

我們使用命令創建這個Service

kubectl apply -f nginx-svc.yaml

查看狀態

[root@k8smaster k8s]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        4d16h
nginx        NodePort    10.104.57.126   <none>        80:30371/TCP   11s

通過集群的任意Node節點IP+30371端口來訪問服務 192.168.182.128:30371 192.168.182.129:30371 192.168.182.130:30371

image.png

6.2 版本升級

我們再來看下剛剛創建的deployment的yml文件 在spec.containers[n].image屬性是填寫鏡像名稱的,這里我們沒有指定版本就是默認拉取latest最新版本的 我們現在把剛剛創建的depolyment刪掉,並指定nginx的版本來創建一下

    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}

刪除deploy和svc

kubectl delete deploy nginx
kubectl delete svc nginx

修改nginx版本指定為1.20

    spec:
      containers:
      - image: nginx:1.20
        name: nginx
        resources: {}

創建nginx

kubectl apply -f nginx.yaml

創建svc

kubectl apply -f nginx-svc.yaml

我們可以通過nodePort端口來訪問nginx查看版本為1.20 image.png

我們現在使用命令升級nginx的版本到1.21

kubectl set image deployment nginx nginx=nginx:1.21

查看升級結果為 deployment "nginx" successfully rolled out

kubectl rollout status deployment nginx

我們看一下nginx的版本

image.png

如果你在執行完升級命令之后去查看pod的狀態你會發現你設置的副本數為3,但是當前的pod絕對大於3個,但是狀態可能不同. 這個升級怎么理解呢,就像一個隊列,先進先出,三個老版本的pod,當有一個新版本的pod起來之后,就會把老版本的剔除掉,知道所有pod都更新換代!這期間,服務不會停止。這就是不停機維護升級版本。

6.3 版本回退

生產情況下,我們把版本升級之后,有用戶返回新版本好像有BUG!影響很大,這個時候怎么辦。k8s也提供了版本回退機制,你可以一鍵回退到上個版本

我們先看一下版本的管理 有兩個版本,第一個就是最開始的1.20,第二個就是我們升級的1.21

[root@k8smaster k8s]# kubectl rollout history deployment nginx
deployment.apps/nginx 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

回滾到上一個版本

kubectl rollout undo deployment nginx

這次我截了個圖 可以明顯看到,下邊三個是新創建的pod, 已經有兩個處於Running狀態了,那就證明上邊必定有兩個已經停止了服務處於Terminating狀態,一會就會消失了~ 和上邊升級版本的過程是一樣的,回退也是不停機的 image.png 查看回滾結果

[root@k8smaster k8s]# kubectl rollout status deployment nginx
deployment "nginx" successfully rolled out

我們既然有了版本管理的概念,那我們能否回滾到某個指定版本呢?是可以的

回滾到指定版本 通過--to-revision回滾到某個版本

kubectl rollout undo deployment nginx --to-revision=2

6.4 彈性伸縮

當生產環境的服務訪問量高峰期時,我們可以動態的增加容器的數量來分擔流量

kubectl scale deployment nginx --replicas=5

6.5 一些特殊的Controller

當我們創建副本數量大於1個時,這些pod啟動是無序的,而且每個pod都是一樣的,可以進行隨意伸縮,我們如果想為每一個pod指定一個唯一標識符,該如何做?如果我們只需要這個pod運行一次就停止,或者定時運行一個pod該怎么做?

6.5.1 StatefulSet

我們上邊創建服務均使用的Deployment,我們可以使用StatefulSet來創建Pod 我們看一下這個yml,中間以 "---"符號隔開代表把兩個yml寫在一起了,創建的時候使用這一個文件即可

首先,我們創建了一個Service,它的name是nginx-sfs,最重要的是它的ClusterIP我們設置了None 接下來創建了一個StatefulSet類型的Controller 這個和前邊是一樣的,只要注意Service和Controller的標簽一致即可。

apiVersion: v1
kind: Service
metadata:
  name: nginx-sfs
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: nginx-sfs
  clusterIP: None
  selector:
    app: nginx

---

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-sfs
spec:
  serviceName: nginx-sfs
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

使用命令創建它,並查看它們的狀態 可以看到nginx-sfs的ClusterIP是None pod的name是以nginx-sfs-序列號來創建的,並且會給每個pod分配一個固定的域名,這個域名解析對應的pod內部IP StatefulSet中每個Pod的DNS格式為statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local 這一塊的東西可以看一下文末的參考鏈接中的官方文檔。

StatefulSet的作用是很大的,例如可以應用到mysql主從,來確定標識進行讀寫分離。

[root@k8smaster k8s]# kubectl apply -f nginx-sfs.yaml 
service/nginx-sfs created
statefulset.apps/nginx-sfs created
[root@k8smaster k8s]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        4d22h
nginx        NodePort    10.111.59.136   <none>        80:32526/TCP   11m
nginx-sfs    ClusterIP   None            <none>        80/TCP         30s
[root@k8smaster k8s]# kubectl get po
NAME                    READY   STATUS    RESTARTS   AGE
nginx-f54648c68-56sqr   1/1     Running   0          44m
nginx-f54648c68-b7b7d   1/1     Running   0          44m
nginx-f54648c68-cv58s   1/1     Running   1          5h57m
nginx-f54648c68-rr2jr   1/1     Running   0          44m
nginx-f54648c68-xmsr2   1/1     Running   0          44m
nginx-sfs-0             1/1     Running   0          4m2s
nginx-sfs-1             1/1     Running   0          3m41s
nginx-sfs-2             1/1     Running   0          3m19s

6.5.2 DaemonSet

加入我們有個服務,是收集每台node節點的性能數據,我們要讓每台node都部署一個這樣的探針,並且后邊新加進來的node節點也要自動部署這個探針服務,我們就可以使用DaemonSet

我們看一下這個yml, kind被標識為DaemonSet,這里邊的volumes后邊會說,我們創建這個,來收集nginx產生的日志,每台node節點都部署一個.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-ds
  labels:
    app: nginx-ds
spec:
  selector:
    matchLabels:
      app: nginx-ds
  template:
    metadata:
      labels:
        app: nginx-ds
    spec:
      containers:
      - name: logs
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: varlog
          mountPath: /tmp/log
      volumes:
      - name: varlog
        hostPath:
          path: /var/log

我們使用命令創建它並查看狀態

[root@k8smaster k8s]# kubectl apply -f nginx-ds.yaml 
daemonset.apps/nginx-ds created
[root@k8smaster k8s]# kubectl get daemonSet
NAME       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
nginx-ds   2         2         2       2            2           <none>          26s
[root@k8smaster k8s]# kubectl get po -o wide
NAME                    READY   STATUS    RESTARTS   AGE     IP            NODE       NOMINATED NODE   READINESS GATES
nginx-ds-4w8wj          1/1     Running   0          80s     10.244.2.22   k8snode2   <none>           <none>
nginx-ds-t6q9w          1/1     Running   0          80s     10.244.1.7    k8snode1   <none>           <none>

可以看到每台node節點都部署了一個nginx-ds服務

6.5.3 Job(一次性任務)

我們要部署一個批處理任務,執行一次即可怎么做? kind指定為Job類型, 容器使用perl,進行圓周率的輸出

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

創建並查看結果

可以看到pod狀態為Completed代表pod的程序已經運行完成, 使用get jobs命令查看job也運行完成

[root@k8smaster k8s]# kubectl get po
NAME                    READY   STATUS      RESTARTS   AGE
pi-ltjzl                0/1     Completed   0          3m19s
[root@k8smaster k8s]# kubectl get jobs
NAME   COMPLETIONS   DURATION   AGE
pi     1/1           80s        3m28s

我們使用命令查看pod的日志看下

[root@k8smaster k8s]# kubectl logs pi-ltjzl
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

6.5.4 CronJob(定時任務)

每隔一段時間我們就讓它執行一次任務的服務怎么部署? 指定kind類型為CronJob schedule參數為cron表達式 這里是每分鍾 具體內容是輸入 Hello from the Kubernetes cluster

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

創建並查看結果 我們可以看到已經運行2m31s,上次運行是34s前 看到有兩個pod的狀態為Completed

[root@k8smaster k8s]# kubectl get cronjob
NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   False     1        34s             2m31s
[root@k8smaster k8s]# kubectl get po
NAME                     READY   STATUS      RESTARTS   AGE
hello-1623684840-v79g9   0/1     Completed   0          110s
hello-1623684900-pw8g2   0/1     Completed   0          50s

查看pod內日志

[root@k8smaster k8s]# kubectl logs hello-1623684900-pw8g2
Mon Jun 14 15:35:46 UTC 2021
Hello from the Kubernetes cluster

要注意的是,定期任務,是每到執行時間便創建一個pod來執行任務,執行完pod狀態就是Completed

至此,我們就可以動態的對集群內部的應用進行管理配置,還有幾種適用於不同場景的Controller, 其中我們創建了Service來對外暴漏nodePort端口來提供服務,這個Service是什么呢?

7.Service

Service 是 Kubernetes 最核心概念, 通過創建 Service,可以為一組具有相同功能的容器應

用提供一個統一的入口地 址, 並且將請求負載分發到后端的各個容器應用上

7.1 Service是什么?

我們先用命令查看一下pod的詳細描述 可以看到IP地址都是虛擬的,Pod這個概念時短暫的,只要你node重啟或者副本伸縮,這個IP都會發生變化, 我們知道,服務之間通信最基本的都要基於IP+端口來傳輸數據,那么IP老是變化怎么辦?那就是Service做的事情了.

[root@k8smaster ~]# kubectl get po -o wide
NAME                    READY   STATUS    RESTARTS   AGE     IP            NODE       NOMINATED NODE   READINESS GATES
nginx-f54648c68-56sqr   1/1     Running   0          2m39s   10.244.2.19   k8snode2   <none>           <none>
nginx-f54648c68-b7b7d   1/1     Running   0          2m39s   10.244.1.4    k8snode1   <none>           <none>
nginx-f54648c68-cv58s   1/1     Running   1          5h15m   10.244.2.18   k8snode2   <none>           <none>
nginx-f54648c68-rr2jr   1/1     Running   0          2m39s   10.244.2.20   k8snode2   <none>           <none>
nginx-f54648c68-xmsr2   1/1     Running   0          2m39s   10.244.1.3    k8snode1   <none>           <none>

可以把Service看作是一個服務注冊中心,創建的pod信息都被注冊到上邊,需要通信的時候,就去服務中心中去發現,找到對應的IP建立鏈接,而且它還可以指定負載策略,五個pod,到底哪個接受請求,就是它做的事情。

Service和pod如何建立關系呢?我們看一下創建Service時候的yml 可以看到labels.app:nginx 和 selector.app:nginx是一致的, 創建deployment時,我們給的標簽也是app:nginx,它們是通過標簽來進行關聯的。

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: NodePort
status:
  loadBalancer: {}

7.1 Service的三種類型

類型 說明
ClusterIP 默認類型,供集群內部使用
NodePort 對外暴漏端口,供外部應用訪問使用
LoadBalancer 對外訪問應用,適用於公有雲

我們看上邊Service的yml文件,其實可以看到我們指定了type:NodePort,這個時候創建svc之后,我們就可以使用任意node節點的IP+端口來訪問我們的服務了~

那默認類型怎么理解呢,如果使用ClusterIP類型,那么就默認只能集群內部訪問,使用任意node節點內部進行curl請求或者其它連接請求即可訪問. 我們可以驗證以下,我們把創建好的svc刪除

[root@k8smaster ~]# kubectl delete svc nginx
service "nginx" deleted
[root@k8smaster k8s]# vi nginx-svc.yaml 
[root@k8smaster k8s]# kubectl apply -f nginx-svc.yaml 
service/nginx created
[root@k8smaster k8s]# kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP   4d22h
nginx        ClusterIP   10.99.42.89   <none>        80/TCP    7s

可以看到我們查詢svc之后,TYPE類型變為ClusterIP,這個時候我們在集群內部任一節點來訪問這個IP 可以看到,訪問到了nginx服務

[root@k8snode1 ~]# curl 10.99.42.89
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

8.Secret

Secret 解決了密碼、 token、 密鑰等敏感數據的配置問題, 而不需要把這些敏感數據暴露

到鏡像或者 Pod Spec 中。 Secret 可以以 Volume 或者環境變量的方式使用

8.1 Secret的三種類型

類型 說明
Opaque base64編碼格式的Secret,常用於存儲密碼、密鑰等
kubernetes.io/dockerconfigjson 用來存儲私有 docker registry的認證信息
ServiceAccount 用來訪問KubernetesAPI,由Kubernetes自動創建,並且會自動掛在到Pod的/run/secrets/kubernetes.io/serviceaccount目錄中

8.2 如何使用Opaque類型的Secret

我們在創建容器的時候通常會給容器內傳入參數變量,我們通過兩種方式來說明如何掛載Secret到Pod中

8.2.1 以變量形式

Opaque都是以base64編碼來傳輸的, 我們先編碼兩個字符串來模擬賬號和密碼 admin -> YWRtaW4= fangpengbo -> ZmFuZ3Blbmdibw==

我們看下邊的yml文件,kind類型為Secret,name為secret-Opaque,有兩個參數一個是username,一個是password,對應的value值是我們上邊進行base64的。

apiVersion: v1
kind: Secret
metadata:
  name: secret-opaque
type: Opaque
data:
  username: YWRtaW4=
  password: ZmFuZ3Blbmdibw==

創建並查看它

[root@k8smaster k8s]# kubectl apply -f secret.yaml 
secret/secret-opaque created
[root@k8smaster k8s]# kubectl get secret
NAME                  TYPE                                  DATA   AGE
secret-opaque         Opaque                                2      15s

我們還是以創建nginx來掛載參數為例 在env屬性下邊我們掛在兩個環境變量,關聯到secret-opaque這個secret 分別取username和password為key

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: nginx
    image: nginx
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: secret-opaque
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: secret-opaque
            key: password

創建之后,進入容器內查看環境變量

[root@k8smaster k8s]# kubectl apply -f secret-var.yaml 
pod/mypod created
[root@k8smaster k8s]# kubectl get po
NAME                    READY   STATUS    RESTARTS   AGE
mypod                   1/1     Running   0          44s
[root@k8smaster k8s]# kubectl exec -it mypod bash
root@mypod:/# echo $SECRET_USERNAME
admin
root@mypod:/# echo $SECRET_PASSWORD
fangpengbo
root@mypod:/# 

8.2.2 以配置文件形式掛載

我們把創建的secret以配置文件的形式直接掛載到pod內部 不一樣的地址在於volumeMounts這個屬性, mountPath說明掛載到容器內部的哪個路徑 volumes就是secret列表

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: secret-opaque

創建並進入容器內部查看它

[root@k8smaster k8s]# kubectl exec -it mypod bash
root@mypod:/# cd /etc/foo/
root@mypod:/etc/foo# ls
password  username
root@mypod:/etc/foo# cat password 
fangpengbo
root@mypod:/etc/foo# cat username 
admin

9.ConfigMap

ConfigMap 功能在 Kubernetes1.2 版本中引入, 許多應用程序會從配置文件、 命令行參數

或環境變量中讀取配 置信息。 ConfigMap 給我們提供了向容器中注入配置信息的機 制, ConfigMap 可以被用來保存單個屬性, 也 可以用來保存整個配置文件或者 JSON 二進 制大對象

configMap和我們上個Secret類似,不過是configMap更像一個配置文件,用來保存一些配置信息供我們的pod讀取.我們來看下如何使用configMap來掛載到pod中

9.1 如何使用configMap

我們現在有一個redis的配置文件如下

redis.host=127.0.0.1
redis.port=6379
redis.password=123456

我們來創建並查看它

[root@k8smaster k8s]# kubectl create configmap redis-config --from-file=redis.properties
configmap/redis-config created
[root@k8smaster k8s]# kubectl get cm
NAME           DATA   AGE
redis-config   1      7s
[root@k8smaster k8s]# kubectl describe cm redis-config
Name:         redis-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
redis.properties:
----
redis.host=127.0.0.1
redis.port=6379
redis.password=123456

Events:  <none>

下邊我們還是創建一個nginx容器,用volume方式掛載到容器內部 我們可以看到在configMap屬性中指定我們剛剛創建的redis-config 創建完pod之后我們輸出以下這個配置文件

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: busybox
      image: busybox
      command: [ "/bin/sh","-c","cat /etc/config/redis.properties" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: redis-config

創建pod並查看日志


下邊我們以變量的形式掛載到pod中 我們創建了一個kind類型為ConfigMap的資源 data綁定了兩個值

apiVersion: v1
kind: ConfigMap
metadata:
  name: myconfig
  namespace: default
data:
  special.level: info
  special.type: hello

創建它

[root@k8smaster k8s]# kubectl apply -f myconfig.yaml 
configmap/myconfig created
[root@k8smaster k8s]# kubectl get cm
NAME           DATA   AGE
myconfig       2      8s

創建pod掛載 我們掛載到env中

apiVersion: v1
kind: Pod
metadata:
  name: mypod1
spec:
  containers:
    - name: busybox
      image: busybox
      command: [ "/bin/sh", "-c", "echo $(LEVEL) $(TYPE)" ]
      env:
        - name: LEVEL
          valueFrom:
            configMapKeyRef:
              name: myconfig
              key: special.level
        - name: TYPE
          valueFrom:
            configMapKeyRef:
              name: myconfig
              key: special.type
  restartPolicy: Never

創建並查看日志

[root@k8smaster k8s]# kubectl logs mypod1
info hello

10.Ingress-Nginx

在前邊我們創建了一個pod,通過service對外暴漏端口,就可以通過任意node節點IP+端口訪問到應用, 但是生產環境我們肯定是通過域名來訪問的,這個時候就要借助Ingress-Nginx來進行代理。

在創建ingress之前,我們先創建一個pod並且暴漏端口

[root@k8smaster k8s]# kubectl get po,svc
NAME                        READY   STATUS      RESTARTS   AGE
pod/nginx-f54648c68-xmsr2   1/1     Running     1          2d2h

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/nginx        NodePort    10.111.59.136   <none>        80:32526/TCP   2d1h

10.1 創建ingress

首先下載ingress的ymal文件,官網也有,這里直接提供. ingress-controller.yaml

使用命令創建就會創建很多東西

[root@k8smaster k8s]# kubectl apply -f ingress-controller.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created

我們查看一下ingress的狀態 注意ingress創建了一個叫 ingress-nginx的名稱空間

[root@k8smaster k8s]# kubectl get po -n ingress-nginx
NAME                                       READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-766fb9f77-85jqd   1/1     Running   0          54s

10.2 創建ingress的規則

我們把ingress和剛剛創建的service關聯起來 host就是我們要綁定的域名,這里我先用本地host驗證以下 下邊的serviceName就是我們創建的nginx對外暴露端口的service名稱 servicePort就是pod的內部端口

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: example-ingress
spec:
  rules:
  - host: example.nginx.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx
          servicePort: 80

創建ingress並查看

[root@k8smaster k8s]# kubectl apply -f ingress.yaml 
ingress.networking.k8s.io/example-ingress created
[root@k8smaster k8s]# kubectl get ing
NAME              CLASS    HOSTS               ADDRESS   PORTS   AGE
example-ingress   <none>   example.nginx.com             80      7s

創建之后,我們要通過ingress來訪問我們的nginx服務,我們先看下ingress部署到哪台node

[root@k8smaster k8s]# kubectl get po -n ingress-nginx -o wide
NAME                                       READY   STATUS    RESTARTS   AGE     IP                NODE       NOMINATED NODE   READINESS GATES
nginx-ingress-controller-766fb9f77-85jqd   1/1     Running   0          7m45s   192.168.182.130   k8snode2   <none>           <none>

可以看到是在k8snode2節點上,對應的IP是192.168.182.130, 我們在本地host做一下映射測試一下 image.png 然后訪問我們映射的域名 image.png 到這里,我們就可以通過ingress來部署域名進行服務的映射了~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM