Google Code Prettify

2020年1月30日 星期四

K8s: volume (PersistentVolumeClaim)

之前整理的「K8s: volume (emptyDir)」,volume 是建立在 pod 上,在 K8s 中 pod 是隨時可被消滅和重建,所以 emptyDir 只能存暫時性資料。這一篇要整理的是 PersistentVolumeClaim,volume 是建立在 cluster 上,不會隨 pod 的消滅而消失,可以用來儲存永久性資料。資料會儲存在 NFS server,怎麼建立 NFS server,可以參考「install nfs server@Ubuntu」。
apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  - ReadOnlyMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /var/nfs/www
    server: 172.31.192.65
在一般的公司,系統資源應該會由系統管理員負責並分配給程式開發人員,K8s 於是設計了系統管理員先在 K8s cluster 上建立 PersistentVolume (PV),分配了一定的資源,程式開發人員想要使用這些資源,要再建立 PersistentVolumeClaim (PVC) 由 PV 中取得,上面的 yaml 建立了 PV,有 1G 的儲存空間,下面的 yaml 建立一個 PVC,自上述的 PV 中取得 100M 的空間。

上面第 12 行的設定值為 Retain,表示就算 PVC 被刪除、回收,PV 不會被刪除且會保留 PVC 裡的資料,但是狀態由 Bound 變為 Released,要重新使用這個 PV,需手動清除上面的資料再將狀態改為 Available; 如果設定為 Delete 則當 PVC 刪除,PV 也會一併刪除。

第 13~15 行是指定 nfs 的 IP 和分享出來的路徑。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  storageClassName: manual
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi
現在實際來執行一下上面兩個 yaml …
注意看上面的執行結果,建立 PV 後,PV 的 STATUS 是 Available,CLAIM 是空白,當 PVC 建立後,PV 的 STATUS 變成 Bound,CLAIM 變成 default/my-pvc,表示 my-pv 確實綁定到 my-pvc 了! 接著建一個 pod 來使用這個 PVC,在 PVC 的 yaml 檔中,不需指定要用那一個 PV,Kubernetes 會根據  PVC 宣告的內容,找到合適的 PV,因為上面的  PVC 有宣告 storageClassName,Kubernetes 會找到 storageClassName 同名的 PV,配置空間給 PVC。

要特別注意的是,PV 和 PVC 是一對一的關係,PVC 不能要求比 PV 更多的空間,但是,如上,當 PVC 要求的比 PV 擁有的少時,PV 擁有的所有資源都會給 PVC,所以,PVC 只要求 100Mi,可是 PV 有 1Gi,PVC 的 CAPACITY 會是 1Gi。
apiVersion: v1
kind: Pod
metadata:
  name: centos-pvc
spec:
  containers:
  - name: centos7
    image: centos:7
    command:
    - "bin/bash"
    - "-c"
    - "sleep 10000"
    volumeMounts:
    - name: logfolder
      mountPath: /home/steven/log
  volumes:
  - name: logfolder
    persistentVolumeClaim:
      claimName: my-pvc
my-pvc 這個 volume 在 pod 中會被 mount 在 /home/steven/log 目錄,可以使用 kubectl exec 進入 pod 中,於該目錄建立一些檔案,然後刪除 pod 再重新建立,再用 kubectl exec 進入新建立的 pod,可以在 /home/steven/log 目錄裡找到剛剛建立的檔案。最後補充說明一下上面的參數。
  • accessModes
    1. RWO (ReadWriteOnce): 僅允許單個 node 掛載讀寫。
    2. ROX (ReadOnlyMany): 允許多個 node 掛載,但 read only。
    3. RWX (ReadWriteMany): 允許多個 node 掛載,且可讀寫。

K8s: volume (gitRepo)

上一篇「K8s: volume (emptyDir)」整理的是最基礎的 emptyDir,這一篇要整理的是 gitRepo,這類型的 volume 也是一種 emptyDir,差別在於它會從指定的 git 上將相關的檔案 clone 到 volume 裡。
apiVersion: v1
kind: Pod
metadata:
  name: host-git
spec:
  containers:
  - name: host-git
    image: centos:7
    command:
    - "bin/bash"
    - "-c"
    - "sleep 10000"
    volumeMounts:
    - name: jar
      mountPath: /home/steven/host
  volumes:
  - name: jar
    gitRepo:
      repository: https://github.com/twleader/RESTAPI.git
      revision: master
      directory: .
和 emptyDir 很類似,差別在於第 18~20 行,指出了 git repository 及 revision,第 21 行則指出 clone 下來的內容要放在 volume 的當前目錄。
如上執行 yaml 後建立起 pod host-git。接下用 exec 指令進入 pod 裡面看一下 /home/steven/host 目錄裡有沒有 clone 下來的檔案。
非常順利。

2020年1月29日 星期三

K8s: volume (emptyDir)

Kubernetes 中的 volume 其實就是一個目錄,可以讓 container 間互相交換資料,或是永久保留資料。volume 提供了非常多種類型,這一篇整理的是最基礎的類型 emptyDir,這個類型的 volume 是建立在 pod 上面,建立時為空目錄,可用於儲存臨時的資料,下面會在一個 pod 上建立兩個 container 及一個 volume。
apiVersion: v1
kind: Pod
metadata:
  name: sharevol
spec:
  containers:
  - name: c1
    image: twleader/host:latest
    volumeMounts:
    - name: xchange
      mountPath: /tmp/logs
    ports:
    - containerPort: 9080
      protocol: TCP
  - name: c2
    image: centos:7
    command:
    - "bin/bash"
    - "-c"
    - "sleep 10000"
    volumeMounts:
    - name: xchange
      mountPath: /tmp/xchange
  volumes:
  - name: xchange
    emptyDir: {}
  1. 第 24~26 行,在 pod 上建立一個 volume 並命名為 xchange。
  2. 第 7~14 行,這是借用之前「部署 RESTful service 到 Kubernetes」已經建立好的一個 image twleader/host,建立一個命名為 c1 的 container。
  3. 第 9~11 行,將 volume xchange 掛載到 /tmp/logs 目錄。
  4. 第 15~23 行,建立命名為 c2 的 container,僅簡單的包含一個 centos。
  5. 第 21~23 行,將 volume xchange 掛載到 /tmp/xchange 目錄。
有了 yaml 就執行命令建立 pod 吧~
接著利用 exec 指令進入 container c1 於 volume 所在目錄建立一個檔案,如下:
再用 exec 指令進入 container c2,於 volume 所在目錄看看有沒有該檔案? 當然有!
同一個 pod 中不同的 container 透過這個方式就可以簡單的交換、共享資料了!

2020年1月28日 星期二

K8s: Service

繼續「K8s: ReplicaSet」的例子,為了讓 Kubernates 之外的程式可以存取到 pod,需要建立 service (服務),當成外界與 pod 間的橋樑。
  • 建立服務
apiVersion: v1
kind: Service
metadata:
  name: host-http
spec:
  ports:
  - name: http
    port: 9090
    targetPort: 9080
  selector:
    app: host
說明一下,這個 yaml 做了些什麼:
  1. 第 4 行定義了服務的名稱為 host-http
  2. 第 8 行指出服務開放給外界訪問的 port 是 9090
  3. 第 9 行指出 pod 的 port 是 9080
  4. 第 10~11 行指出 pod 上有 label "app=host" 的綁定到這個服務
產生服務後,以如下指令看一下服務內容。
kubectl describe svc host-http
從上面的內容可以看到服務分配到的 IP 是 10.108.180.52 (cluster IP),三個 pod 分配到的 IP 是 10.244.1.42、10.244.1.43、10.244.1.44,這些 IP 都是 Kubernetes 內部的 IP,無法由外部訪問,要測試服務是否正常工作,可以跳入一個 pod,由它對服務發出 request,如下:
kubectl exec host-2vxjs -- curl -s http://10.108.180.52:9090/host/info
注意看到,每次執行不一定會使用到同一個 pod。
  • 發現服務
  • Kubernetes 配發給服務的 IP 在服務整個生命週期不會變,但是,pod 要如何知道服務的 cluster IP ? 我們先刪除所有的 pod,rs 會自動再產生 3 個新的 pod。
    kubectl exec host-kxdtn env
    
    如上,跳入一個 pod 查看環境變數,內容如下,為什麼上面要故意刪除 pod 讓 rs 重新產生? 因為 pod 被新創立時,會根據 Kubernetes 最新狀況生成環境變數,記錄在 pod 裡面。
    PATH=/usr/local/jdk-11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    HOSTNAME=host-kxdtn
    HOST_HTTP_PORT_9090_TCP=tcp://10.108.180.52:9090
    HOST_HTTP_PORT_9090_TCP_PROTO=tcp
    HOST_HTTP_PORT_9090_TCP_PORT=9090
    KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
    HOST_HTTP_SERVICE_HOST=10.108.180.52
    HOST_HTTP_SERVICE_PORT=9090
    KUBERNETES_SERVICE_PORT_HTTPS=443
    KUBERNETES_PORT=tcp://10.96.0.1:443
    KUBERNETES_PORT_443_TCP_PORT=443
    KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
    HOST_HTTP_SERVICE_PORT_HTTP=9090
    HOST_HTTP_PORT=tcp://10.108.180.52:9090
    HOST_HTTP_PORT_9090_TCP_ADDR=10.108.180.52
    KUBERNETES_SERVICE_HOST=10.96.0.1
    KUBERNETES_SERVICE_PORT=443
    KUBERNETES_PORT_443_TCP_PROTO=tcp
    JAVA_HOME=/usr/local/jdk-11
    HOME=/root
    
    注意看到第 7~8 行,顯示了服務的 cluster IP (10.108.180.52) 及開放出來的 port (9090),新創建的 pod 透過環境變數即可得知。除了這個辦法還有沒有其它辦法? 通常在網路上發現各項服務是透過 DNS,Kubernetes 是否有相同的方式? 答案是有! Kubernetes 提供了 DNS 服務,每個服務的名稱會對映到它的 IP,同時透過 FQDN,這記錄在每個 pod 的 /etc/resolv.conf 檔裡,當服務名稱找不到 IP 時,會根據這個檔的設定加上後綴,再尋找看看,現在再跳入一個 pod 來試看看。
    如上,跳入 pod host-kxdtn,並執行 bash,這樣就可以在裡面下指令,在 pod 裡面要對服務 request,最簡單的就是以服務的名稱當成網址,上面有 ping 看看,真的可以得到服務的 IP,下了 curl http://host-http:9090/host/info 也得到正確的回應,當然啦~ 服務的 port 仍需從環境變數才能得到。
  • 開放服務給客戶端
  • 上面的做法雖然讓 Kubernetes 內的 pod 可以透過服務互相訪問,但是,上面建立服務的方式,沒有將服務暴露出來給外部,K8s 外的客戶端訪問不到,現在修改 yaml 如下:
    apiVersion: v1
    kind: Service
    metadata:
      name: host-http
    spec:
      type: LoadBalancer
      ports:
      - name: http
        port: 9090
        targetPort: 9080
      selector:
        app: host
    
    加入第 6 行的設定,設定 K8s cluster 的架構為 LoadBalancer,刪除服務 host-http 並用這個 yaml 重新建立,接著查詢服務,可以得到如下結果,如果 Kubernetes 有支援負載平衡,在 EXTERNAL-IP 欄位會顯示出開放給外部的 IP,我的環境是自建的私有雲沒有支援,type 被降為 NodePort,該欄位會一直顯示 <pending>,這時候就只能使用 master node 的 IP 為 EXTERNAL-IP,port 如下面顯示的是 32015。
【番外篇】
Kubernetes 本身沒有提供負載平衡的支援,如果是使用 GCP、AWS、Azure … 就會由這些平台提供,為了解決這個問題,出現了 MetalLB 這個解決方案,依該網站提供的安裝程序安裝後,重新建立 service,EXTERNAL-IP 就會有對外 IP 了。

K8s: ReplicaSet

Kubernetes 管理 Pod 的方式是透過 ReplicationController 或 ReplicaSet,不是直接管理 Pod,ReplicaSet 是後來才有的資源,最終會完全取代 ReplicationController,也就是說 ReplicationController 在不久後的版本就會被棄用,所以這裡只整理 ReplicaSet 相關說明。

在「K8s: cluster (hostname & IP)」中,有使用到如下的指令擴容 pod host 成 3 個,並交給讓 ReplicaSet 管理。
kubectl scale rs host --replicas=3
在「部署 RESTful service 到 Kubernetes」中更有使用到如下的 yaml 直接由 image 創立 3 個 pod 交給 ReplicaSet 管理,這裡針對 yaml 做個說明。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: host
spec:
  replicas: 3
  selector:
    matchLabels:
      app: host
  template:
    metadata:
      labels:
        app: host
    spec:
      containers:
      - name: host
        image: twleader/host
  1. 第 2 行指出要建立的資源是 ReplicaSet
  2. 第 4 行指出 ReplicaSet 的名稱為 host
  3. 第 6 行表示會產生 3 個 pod
  4. ReplicaSet 管理 pod 的方式是透過 label,第 7~9 行指出當 pod 有 app=host 的 label 時,就納入這個 rs host 管理。
  5. 第 10 行 template 以下,說明了要創建 pos,包括第 12~13 行指出每個 pod 會有 label "app=host",第 14~17 行則指出,pod 是由 image twleader/host 產生,並且 pod 的名稱為 host。
ReplicaSet 怎麼管理 pod ? 如下:

也就是說,只要有納入 rs 管理的 pod,當 pod 因為各種原因被判定為已經停止運轉,rs 會重新啟動 pod,但是如果是 pod 被刪除,則 rs 會創立一個新的 pod。下面要開始來實驗看看 …
我把 pod、rs 都刪光了,所以現在什麼都沒有 …
kubectl create -f host-rs.yaml
使用上面的 yaml 創立一個命名為 host 的 rs,及三個命名為 host-xxxxx 的 pod 交給這個 rs 管理,特別注意,這三個 pod 都有 Label "app=host"。
現在我們要刪除一個 pod,看看 rs 有什麼反應?
如上,我刪除了 pod host-dfsrc,rs 立刻又建立了一個新的 pod host-tft2x。如果我把 pod 的 label 改掉呢?
kubectl label pods host-678mg "app=hostX" --overwrite
我改掉了 po host-678mg 裡的 label 讓它變成 "app=hostX",因為 rs host 管理的是 label "app=host" 的 pod,ReplicaSet 偵測到符合的 pod 只剩兩個,所以又創建了一個 po host-c492q。
這個時候因為 pod host-678mg 沒有被任何 ReplicaSet 管理,它的存續與否不會被偵測管理。上面實驗證實了,當實際的 pod 少於預期時,ReplicaSet 會創立新的 pod,那麼,如果 pod 超過預期呢? ReplicaSet 真的會將它減少為 3 個嗎?
如上,我們觀察到了,當我把 host-678mg 的 label 改回 "app=host",ReplicaSet 偵測到符合條件的 pod 有四個,比預期的多一個,這時候就從已存在的四個中排一個刪除。上面我的刪除後查詢的太快,顯示出要被刪除的 pod 狀態為 Terminating,重新查詢後,才真的只剩 3 個。

最後,我們可以用以下指令查詢 rs 的詳細說明:
kubectl describe rs

實驗做完了,我們想刪除 rs 及所有的 pod,只要刪除 rs 即可,rs 所管理的 pod 會被一併刪除。

2020年1月27日 星期一

Docker / Kubernetes / Elastic Stack

項目日期
*
  Docker
1Installing and Configuring Docker
2017/09/04
2docker 基本指令
2018/01/03
3製作包含 java 的 docker 鏡像
2018/01/05
4使用 Dockerfile 創建 docker 鏡像
2018/01/06
5部署 RESTful service 到 docker
2019/04/05
6docker logs
2018/02/27
7安裝 Harbor (Ubuntu)
2020/04/10
8安裝 SQL Server@Docker
2020/09/12
*
  Kubernetes (K8s) / OpenShift Container Platform (OCP)
1安裝 Minikube
2019/03/23
2install Kubernetes cluster@Macbook M2
2023/10/28
3部署 RESTful service 到 Kubernetes
2020/01/08
4K8s: cluster (install)
2020/01/25
5K8s: cluster (hostname & IP)
2020/01/26
6K8s: namespace
2020/01/27
7K8s: ReplicaSet
2020/01/28
8K8s: Service
2020/01/28
9K8s: volume (emptyDir)
2020/01/29
10K8s: volume (gitRepo)
2020/01/30
11K8s: volume (PersistentVolumeClaim)
2020/01/30
12K8s: Ingress
2020/04/14
13K8s: 設定環境變數
2020/04/15
14K8s: ConfigMap
2020/04/16
15K8s: Deployment
2020/04/17
16安裝 CodeReady Container
2022/03/29
*
  Elastic Stack
1安裝 docker-elk
2020/05/03

K8s: namespace

Kubernates 提供有 namespace (命名空間) 以方便管理,這應該不難理解,現在的程式語言也多半有提供命名空間 (例如 Java 的 package),終究系統大了的話,名稱很容易重複。底下整理一些 K8s 命名空間的基本觀念。
  • 列出命名空間
  • kubectl get ns
    
    Kubernates 安裝好,預設就會有這些命名空間,在建立各項資源時,如果沒有特別指命,預設使用 default。
  • 建立命名空間
  • kubectl create namespace my-ns
    
    這是使用指令的方式,另外也可以使用 yaml,如下:
    apiVersion: v1
    kind: Namespace
    metadata:
      name: my-ns
    
    命名空間的名稱,限定只能用英數字及 dash (-)。
  • 在指定的命名空間建立資源
  • 使用如下的 yaml 重新建立一次 pod host:
    apiVersion: v1
    kind: Pod
    metadata:
      name: host
      labels:
        app: host-info
    spec:
      containers:
      - name: host
        image: twleader/host:latest
        ports:
        - containerPort: 9080
    
    指令則是在原有指令後面指定命名空間:
    kubectl create -f create-pod.yaml -n my-ns
    
    建立成功後,檢查看看,確定真的建立在命名空間 my-ns 裡了!
  • 命名空間的隔離
  • 命名空間只會隔離名稱,也就是相同名稱可以存在於不同的命名空間,但是,不同命名空間的資源,仍可互相存取到對方,不會受到任何限制!
  • 刪除資源
  • 先檢查一下目前 Kubernetes 中的 pod ... 接著我們要刪除 pod host,如下:
    kubectl delete po host
    
    因為沒有指定要刪除那一個命名空間的 pod host,預設會刪除 default 裡資源,檢查如下,default 裡的被刪除了,my-ns 的還在!
    指定命名空間,重新刪除一次,如下,正確的刪除了 my-ns 命名空間裡的 pod host。
    當 Kubernetes 收到刪除 pod 的指令,會向 pod 中的所有 container 發出 SIGTERM 的信號,並等待一段時間 (通常為 30秒) 待所有 container 正常關閉,如果時間到仍有 container 未關閉,則會直接以 SIGKILL 將它的 process 終止! 為了確保程式可以正常結束,最好可以在程式中正確的處理 SIGTERM 信號。
  • 刪除命名空間
  • kubectl delete ns my-ns
    
    這樣的指令會把命名空間及其下所有資源刪除! 如果希望只刪除命名空間下的 pod,但保留命名空間呢? 如下:
    kubectl delete po --all --namespace my-ns
    
    如果要刪除命名空間下(幾乎)所有資源呢? 指令如下:
    kubectl delete all --all --namespace my-ns 
    
    為什麼說是"幾乎"而不是全部? 因為 Pod、ReplicationController、ReplicaSet、Service 真的會被刪除,但是有少數的資源,例如 Secret 不會被刪除。
  • 切換命名空間
  • 登入 kubernetes 時,沒有特別指定,使用的是 default 這個命名空間,建立資源時,沒有指定命名空間,都會建立在 default 裡,如果我們的資源都在別的命名空間,最好是切換到那個命名空間去,這樣建立的資源就會預設是在那個命名空間,怎麼切換呢? 指令有點長,一般會先建立個 alias,如下:
    alias kcd='kubectl config set-context $(kubectl config current-context) --namespace '
    
    之後要切換命名空間,例如,有個命名空間為 home,要切換到 home,就這麼下指令:
    kcd home
    

2020年1月26日 星期日

K8s: cluster (hostname & IP)

接續上一篇「K8s: cluster (install)」,在安裝了 Kubernetes cluster 之後,現在要來觀察裡面的配置。
  • 部署一個 demo 程式
  • @RestController
    @RequestMapping("/host")
    public class HostController {
        @GetMapping("/info")
        public String info() {
            try {
                InetAddress ip = InetAddress.getLocalHost();
                return ip.getHostName() + " (" + ip.getHostAddress() + ")\n";
            } catch (UnknownHostException e) {
            }
            
            return "Unknown\n";
        }
    }
    
    這個 REST API 非常簡單,每次被呼叫就會傳回 hostname 和 ip,這樣我們就可以觀察安裝好 Kubernetes cluster 之後,倒底 Kubernetes 進行了怎麼樣的配置。部署的細節可以參考「部署 RESTful service 到 Kubernetes」,這裡假設已經部署了一個命名為 host 的 pod,並且服務名稱為 host-http。為了觀察,這裡先執行以下指令,讓 Kubernetes 把 Pod 增加為 3 個。
    kubectl scale rs host --replicas=3
    
  • IP & hostname
  • kubectl get pod
    
    要怎麼知道真的有 3 個 Pod ? 如上指令,可以看到紅框裡新產生的三個 Pod,並且知道這 3 個 Pod 的 hostname 分別為 host-24kw7、host-g7xdt、host-prlrl。那麼這 3 個 Pod 的 IP 呢? 如下 …
    kubectl describe svc host-http
    
    這個指令會把服務的詳細內容列出來,從這裡顯示的資訊可以了解,這 3 個 Pod 的 IP 分別為 10.244.1.14、 10.244.1.16、10.244.1.17,並且知道服務對內的 IP 為 10.106.92.190 (cluster ip)。那麼對外的 IP 呢? 就是機器本身的 IP,master node 的 IP 是 192.168.0.110,來測試一下 …
    curl http://192.168.0.110:32738/host/info
    
    真的顯示出其中一個 Pod 的 hostname 和 IP。上面的方法可以得到 hostname 和 IP,但是倒底那一個 hostname 對到那個 IP 卻不知道,如下的指令可以得到資訊。
    kubectl get pods -o wide
    
    根據上面的資訊,可以得知整個架構如下:
    為什麼 pod 的 port 是 9080? 因為我在程式的 application.properties 定義了如下的參數:
    server.port=9080
    server.servlet.context-path=/
    
    【番外篇1】
    pod 隨時可以被消滅,每次建立時 IP 並不一定會一樣,pod 間要相互訪問,可以透過 cluster IP,例如:
    kubectl exec host-g7xdt -- curl -s http://10.106.92.190:9080/host/info
    
    上述的指令是直接進到指定的 pod host-g7xdt,下 curl 指令。
    【番外篇2】
    不通過服務 (service) 的情況下與特定的 pod 通訊也是有辦法,Kubernetes 提供的轉發的機制,使用 kubectl port-forward,方法如下:
    kubectl port-forward host-g7xdt 10080:9080
    
    我把外部的 port 10080 對映到 host-g7xdt 的 port 9080,執行後會如下:
    接著開啟另一個 terminal,執行 curl 測看看,如下,仍可得到正確的回應。

    2020年1月25日 星期六

    K8s: cluster (install, CentOS 7)

    Kubernates 雖然很火紅,但是到現在安裝仍非常不容易,網路上、書上寫的安裝程序,照著做要一次就成功不容易,多半會遇到一些問題需要解決。這裡整理的是我最近成功安裝的步驟,照著做是否一定會成功呢? 短期內沒問題,長期來說就又不確定了,因為 Kubernetes 發展的太快,會一直有變化。
    • 環境
    1. 兩台電腦,都是 CentOS 7,ip 及 hostname 分別為 192.168.0.110 / master-node、192.168.0.106 / worker-node。
    2. 為了讓兩台電腦可以互通,先在兩台電腦的 /etc/hosts 中加入如下內容:
      192.168.0.110   master-node
      192.168.0.106   worker-node
      
    3. Docker version 19.03.5
    4. Kubernates version 1.17.2
    • 安裝 Docker
    1. 將作業系統裡的套件更新到最新
    2. yum update
    3. 移除舊版 docker
    4. yum remove docker \
                        docker-client \
                        docker-client-latest \
                        docker-common \
                        docker-latest \
                        docker-latest-logrotate \
                        docker-logrotate \
                        docker-engine
      
    5. 安裝相依的套件
    6. yum install -y yum-utils device-mapper-persistent-data lvm2
      
    7. 設定要使用的 docker repository
    8. yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
      
    9. 安裝 docker
    10. yum install docker-ce docker-ce-cli containerd.io
      
      這樣會安裝最新版的 docker。
    11. 啟動 docker
    12. systemctl start docker
      
    13. 設定成自動啟動
    14. systemctl enable docker
      
      這樣設定,每次開機服務會自行動動。
    15. 設定 cgroup-driver
    16. 編輯 /etc/docker/daemon.json,內容如下,如果沒有這個檔案,直接創建一個。
      {
        "exec-opts": ["native.cgroupdriver=systemd"],
        "log-driver": "json-file",
        "log-opts": {
          "max-size": "100m"
        },
        "storage-driver": "overlay2",
        "storage-opts": [
          "overlay2.override_kernel_check=true"
        ]
      }
      
      最重要的是把 native.cgroupdriver 設為 systemd,後面安裝 K8s 時,也要調整相關設定,否則安裝會失敗。
    17. 重啟 docker
    18. systemctl daemon-reload
      systemctl restart docker
      
    • 安裝 Kubernates
    1. 設定環境
    2. setenforce 0
      sed -i 's@SELINUX=enforcing@SELINUX=disabled@' /etc/sysconfig/selinux
      swapoff -a
      
      Kubernetes 基於效能的理由,規定 swap 要關閉,所以要如上設定,設定好要檢查 /etc/fstab,看看最後一行有沒有加上 #,如果沒有加上註解,就自行加上去,這樣重開機後 swap 才不會又被打開。
    3. 關閉防火牆
    4. systemctl disable firewalld && systemctl stop firewalld
      echo 1 > /proc/sys/net/ipv4/ip_forward
      echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
      echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.conf
      modprobe br_netfilter
      echo "br_netfilter" > /etc/modules-load.d/br_netfilter.conf
      sysctl -p
      lsmod | grep br_netfilter
      
      也可以只關閉 Kubernetes 會用到的防火牆,但是,port 很多,這裡直接把防火牆關閉,方便後面的設定。
    5. 設定 Kubernates 的 repository
    6. 檢查一下 /etc/yum.repos.d/kubernetes.repo 的內容是否如下? 沒有這個檔案可以自行新增。
      [kubernetes]
      name=Kubernetes
      baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
      enabled=1
      gpgcheck=1
      repo_gpgcheck=1
      gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
      
    7. 安裝 Kubernates
    8. yum install kubelet-1.17.2 kubectl-1.17.2 kubeadm-1.17.2 --nogpgcheck --disableexcludes=kubernetes
      
      在 cluster 環境,master 及每一個 node 都要有 kubeadm。
    9. 設定 cgroup-driver=systemd
    10. 在 /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf 中加入 KUBERLET_CGROUP_ARGS 參數,並將這個參數加入啟動的參數中,內容如下:
      [Service]
      Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
      Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
      # This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
      EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
      # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
      # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
      EnvironmentFile=-/etc/sysconfig/kubelet
      Environment="KUBELET_CGROUP_ARGS=--cgroup-driver=systemd"
      ExecStart=
      ExecStart=/usr/bin/kubelet $KUBELET_CGROUP_ARGS $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS
    11. 啟動 kubelet
    12. systemctl daemon-reload
      systemctl restart kubelet.service
      
      啟動後沒有成功的話,也不一定會出現明顯的錯誤訊息,要使用如下指令看一下,是否出現 active (running)
      systemctl status -l kubelet.service
      
    13. master 初始化
    14. kubeadm init --apiserver-advertise-address=192.168.0.110 --pod-network-cidr=10.244.0.0/16 --service-cidr=10.96.0.0/12 --kubernetes-version=v1.17.2 --cri-socket="/var/run/dockershim.sock" --ignore-preflight-errors all
      
      這段訊息很重要, 先照著上一個框的三個指令做,下面的框中的指令則是 worker-node 要加入 cluster 的指令,先執行這三個指令。
      mkdir -p $HOME/.kube
      cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
      chown $(id -u):$(id -g) $HOME/.kube/config
      
    15. 安裝通用的 flannel容器網路介面CNI(Container Network Interface)元件
    16. kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
      
    這樣 master node 大功告成了!!
    • worker node 加入 cluster
    worker node 和 master node 的安裝差不多,先在 worker node 上重複上面 docker 的安裝及 Kubernetes 安裝的第 1~6 步驟,接著執行如下指令:
    kubeadm join 192.168.0.110:6443 --token pwwfr9.9w1dn0lq70mraguk --discovery-token-ca-cert-hash sha256:4dcad4f89e0fd0246e8d19efd49d35d6c0f77e9481a6a999536a4775cc217024
    
    過個兩分鐘,應該就可以加入 cluster,如何驗證? 回到 master node 上,執行如下指令,可以看到 Kubernetes 有兩個 node。
    kubectl get nodes
    

    2020年1月8日 星期三

    部署 RESTful service 到 Kubernetes

    這篇將接續「部署 RESTful service 到 docker」,將 docker image 部署到 Kubernetes!
    • push docker image 到 Docker Hub
    docker tag steven/hello twleader/hello
    docker push twleader/hello
    
    我在 Docker Hub 上有個帳號 --- twleader, 所以先將 docker image 加上標簽,命名為 twleader/hello, 然後推送到 Docker Hub,應該可以在 Docker Hub 看到如下的結果。
    • 部署 docker image 到 Kubernetes
    kubectl run hello --image=twleader/hello --port=9080 --generator=run-pod/v1
    
    上述指令指出,要部署的 image 為 twleader/hello,部署上去後 pod 的名稱為 hello,K8S 要 listen 9080 port。使用 kubectl get rc 及 kubectl get pods 指令查看結果,如下圖。Kubernetes 先創建一個 ReplicationController 後,再於其上創建一個 pod。
    執行到這,外部還沒辦法訪問到該 REST API,需要創建一個服務,將這個 ReplicatoinController 暴露出來給外部。
    • 創建服務
    kubectl expose rc hello --type=LoadBalancer --name hello-http
    
    如上指令,創建一個命名為 hello-http 的服務,將剛剛創建的 rc 暴露給外部。以 kubectl get svc 查看,可以看到如下結果。
    如果是在真的 Kubernetes 上執行,應該等個幾分鐘,EXTERNAL-IP 那邊會出現外部的 IP,但是我是用 minikube,要知道暴露給外部的 IP 需要用 minikube service 指令查看,如下圖,得知是 192.168.99.100:31374。
    • 測試
    curl 192.168.99.100:31374/hello/Steven
    順利得到如下結果:

    【番外篇1】
    上面創建服務的小節裡,當創建完服務,minikube 主動幫我們配置對外的 IP、port,是否可以繞過 service,直接與 pod 的連線呢? 方法如下:
    kubectl port-forward hello-rvx2n 8888:9080
    
    外面對 port 8888 的 request 直接 forward 到 pod hello-rvx2n 的 port 9080,測試一下 ...
    【番外篇2】
    ReplicationController 即將被淘汰,改用 ReplicaSet 取代,上面建立 rc 的步驟這裡改成用 rs,可以先建立一個 yaml 檔,如下:
    apiVersion: apps/v1
    kind: ReplicaSet
    metadata:
      name: hello
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: hello
      template:
        metadata:
          labels:
            app: hello
        spec:
          containers:
          - name: hello
            image: twleader/hello
    
    然後下以下指令,建立 rs:
    kubectl create -f hello-replicaset.yaml
    
    接著建立服務將它暴露給外部:
    kubectl expose rs hello --type=LoadBalancer --port 9080 --name hello-http
    
    【番外篇3】
    上面用 kubectl expose 建立服務後,Kubernetes 給了這個服務一個外部 IP,讓 Kubernetes 外的程式可以訪問到 pod 裡的 REST API。事實上,Kubernetes 內部所有 Pod 會有個內部 IP,服務也會有個內部 IP,使用 kubectl exec 指令,直接連結到某個 Pod,也可以測試,指令如下:
    kubectl exec hello-rvx2n -- curl -s http://10.96.217.26:9080/hello/Steven