修改 Kubernetes Kube Proxy IPVS scheduler mode

由於 kube-proxy 在 ipvs scheduler 的模式是 rr,這篇會教學如何在 kubernetes cluster 裡面改變 ipvs scheduler 的模式。

環境

目前有三台 node 所組成的 kubernetes cluster

1
2
3
4
5
# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
node1 Ready master 5d7h v1.16.6 192.168.0.121 <none> Ubuntu 16.04.5 LTS 4.15.0-88-generic docker://18.9.7
node2 Ready master 5d7h v1.16.6 192.168.0.146 <none> Ubuntu 16.04.5 LTS 4.15.0-88-generic docker://18.9.7
node3 Ready <none> 5d7h v1.16.6 192.168.0.250 <none> Ubuntu 16.04.5 LTS 4.15.0-88-generic docker://18.9.7

Kubernetes 裡面有一個 hello world deployment,並使用 NodePort 暴露 8080 port 的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/hello-868dc85486-8lvps 1/1 Running 0 3d2h
pod/hello-868dc85486-j2q7x 1/1 Running 0 3d2h
pod/hello-868dc85486-n2lvt 1/1 Running 0 3d2h
pod/hello-868dc85486-njcfn 1/1 Running 0 3d2h

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 5d8h
service/node-port NodePort 10.233.2.107 <none> 8080:31191/TCP 35h

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello 4/4 4 4 3d2h

NAME DESIRED CURRENT READY AGE
replicaset.apps/hello-868dc85486 4 4 4 3d2h

使用 ipvsadm 查看 ipvs 的 scheduler 模式,目前的模式是 rr。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 169.254.25.10:31191 rr
-> 10.233.90.16:8080 Masq 1 0 0
-> 10.233.92.16:8080 Masq 1 0 0
-> 10.233.92.17:8080 Masq 1 0 0
-> 10.233.96.12:8080 Masq 1 0 0
TCP 172.17.0.1:31191 rr
-> 10.233.90.16:8080 Masq 1 0 0
-> 10.233.92.16:8080 Masq 1 0 0
-> 10.233.92.17:8080 Masq 1 0 0
-> 10.233.96.12:8080 Masq 1 0 0
TCP 192.168.0.121:31191 rr
-> 10.233.90.16:8080 Masq 1 0 0
-> 10.233.92.16:8080 Masq 1 0 0
-> 10.233.92.17:8080 Masq 1 0 0
-> 10.233.96.12:8080 Masq 1 0 0
TCP 10.233.90.0:31191 rr
-> 10.233.90.16:8080 Masq 1 0 0
-> 10.233.92.16:8080 Masq 1 0 0
-> 10.233.92.17:8080 Masq 1 0 0
-> 10.233.96.12:8080 Masq 1 0 0
...

下一節將說明如何修改 ipvs scheduler 的方法。

方法

首先,IPVS scheduler 的設定是在 kube-proxy 的 ConfigMap 裡面。

1
kubectl edit cm kube-proxy -n kube-system

data.config.conf.ipvs.scheduler 裡的參數修改成其他的 scheduler 模式:

1
2
3
4
data:
config.conf:
ipvs:
scheduler: sh

Service: IPVS proxy mode 裡面有提供以下的 scheduler 的模式:

  • rr: round-robin
  • lc: least connection (smallest number of open connections)
  • dh: destination hashing
  • sh: source hashing
  • sed: shortest expected delay
  • nq: never queue

或參考 IPVS wiki

刪除原有的 kube-proxy 的 pod,daemonset.apps/kube-proxy 會自動重新建立新的 kube-proxy Pod。

1
kubectl delete -n kube-system $(kubectl get all -n kube-system | grep pod/kube-proxy | cut -d ' ' -f 1)

再次使用 ipvsadm 查看 ipvs 的 scheduler 就會是修改後的狀態。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 169.254.25.10:31191 sh
-> 10.233.90.16:8080 Masq 1 0 0
-> 10.233.92.16:8080 Masq 1 0 0
-> 10.233.92.17:8080 Masq 1 0 0
-> 10.233.96.12:8080 Masq 1 0 0
TCP 172.17.0.1:31191 sh
-> 10.233.90.16:8080 Masq 1 0 0
-> 10.233.92.16:8080 Masq 1 0 0
-> 10.233.92.17:8080 Masq 1 0 0
-> 10.233.96.12:8080 Masq 1 0 0
TCP 192.168.0.121:31191 sh
-> 10.233.90.16:8080 Masq 1 0 0
-> 10.233.92.16:8080 Masq 1 0 0
-> 10.233.92.17:8080 Masq 1 0 0
-> 10.233.96.12:8080 Masq 1 0 0
TCP 10.233.90.0:31191 sh
-> 10.233.90.16:8080 Masq 1 0 0
-> 10.233.92.16:8080 Masq 1 0 0
-> 10.233.92.17:8080 Masq 1 0 0
-> 10.233.96.12:8080 Masq 1 0 0

Reference

設定 Google Cloud Platform 第三方服務金鑰

這次是將現有的 Google Cloud Functions (GCF) 專案透過 GitHub Actions 自動化部屬至 Google Cloud Platform (GCP)。GitHub Actions 是第三方的服務,GCP 須建立金鑰給第三方服務使用,這篇將會記錄要如何設定 GCP 的金鑰。

環境

  • Google Cloud Platform: 需先建立專案,並開啟帳單功能,ProjectID 為 short-url-256304。

方法

建立憑證金鑰

  1. 點選「 IAM 與管理員」
  2. 點選「服務帳戶」
  3. 點選「建立服務帳戶」

GCP IAM STEP 1

  1. 輸入服務帳戶名稱
  2. 點選「建立」

GCP IAM STEP 2

  1. 點選「角色」-> 選擇要部署要應用程式「XXX開發人員」(下圖以 Gloud Functions 為例) -> 點選「建立」

GCP IAM STEP 3

  1. 點選「建立金鑰」

GCP IAM STEP 4

  1. 選擇金鑰類型「JSON」
  2. 點選「建立」,此時會下載一份金鑰檔,將這份檔案保存好
  3. 點選「完成」

GCP IAM STEP 5

  1. 建立完成畫面

GCP IAM STEP 6

賦予服務帳戶權限

這時候根據建立完還不能透過第三方部署應用程式,會出現以下錯誤訊息:

1
2
3
ERROR: (gcloud.functions.deploy) ResponseError: status=[403], code=[Forbidden], message=[Missing necessary permission iam.serviceAccounts.actAs for ***@appspot.gserviceaccount.com on project ***. 
Please grant ***@appspot.gserviceaccount.com the roles/iam.serviceAccountUser role.
You can do that by running 'gcloud projects add-iam-policy-binding *** --member=***@appspot.gserviceaccount.com --role=roles/iam.serviceAccountUser'

錯誤訊息有提示你要如何解決這個問題,所以照提示訊息輸入以下指令,需將 PROJECT_IDDEPLOY_NAME 改成你現在使用的專案:

1
2
3
4
export PROJECT_ID=short-url-256304
export DEPLOY_NAME=deploy-to-gcp

gcloud iam service-accounts add-iam-policy-binding $PROJECT_ID@appspot.gserviceaccount.com --member=serviceAccount:$DEPLOY_NAME@$PROJECT_ID.iam.gserviceaccount.com --role=roles/iam.serviceAccountUser --project=$PROJECT_ID

操作步驟:

  1. 點選右上角圖示「啟用 Cloud Shell」
  2. 在終端機上輸入賦予帳戶權限的指令

GCP IAM STEP 7

測試

使用的 Repo 是 akiicat/short-url,透過 GitHub Actions 自動部署至 Google Cloud Functions 的設定檔,注意 jobs.deploy.steps[] 裡的前兩個步驟:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# .github/workflows/google-cloud-functions.yml
name: GoogleCloudFuncitons
on: [push]
jobs:
deploy:
name: GCP Authenticate
runs-on: ubuntu-latest
steps:
- name: Setup Google Cloud
uses: actions/gcloud/auth@master
env:
# https://github.com/actions/gcloud/issues/13#issuecomment-511596628
GCLOUD_AUTH: ${{ secrets.GCLOUD_AUTH }}

- name: GCP Setup Project ID
uses: actions/gcloud/cli@master
with:
args: "config set project ${{ secrets.ProjectID }}"

- name: GCP Functions List
uses: actions/gcloud/cli@master
with:
args: "functions list"

- name: Deploy to GCP
uses: actions/gcloud/cli@master
with:
args: "functions deploy link --entry-point=Link --runtime=go111 --trigger-http"

第一個步驟(Setup Google Cloud),是使用先前下載 JSON的金鑰向 Google Cloud Platform 認證權限,這裡的 GCLOUD_AUTH 需先將 JSON 檔轉成 base64 的格式,可以參考這篇 issue

1
2
3
$ base64 short-url-256304-287.json
ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAic2hvcnQtdXJsLTI1NjMwNCIsCiAgI...
lcGxveS10by1nY2YlNDBzaG9ydC11cmwtMjU2MzA0LmlhbS5nc2VydmljZWFjY291bnQuY29tIgp9Cg==

第二個步驟(GCP Setup Project ID),是設定 Google Cloud Platform 的 Project ID

secrets.GCLOUD_AUTHsecrets.ProjectID 的設定請參考 Virtual environments for GitHub Actions

Reference

RBAC Kubernetes 安裝 helm

環境

  • Google Kubernetes Engine
  • Kubernetes version 1.12.8
  • Helm version 2.14.1

安裝 Helm without RBAC

Helm 是 Kubernetes 的管理工具,Tiller 則是 API server,至於之間的差別,Helm 是 Client 端,Tiller 是 Server 端。輸入以下指令會安裝 HelmTiller

1
helm init

官方文件建議加上 –history-max 參數,限制最大歷史指令的數量,如果沒有設定最大歷史記錄,則永遠保留歷史記錄。

1
helm init --history-max 200

##設定 RBAC

在雲端平台上,由於有安全性的問題,如果不設定 RBAC,之後可能會無法正常使用,所以需要提供足夠的權限給 tiller,輸入以下指令建立權限,ServiceAccount 的名稱為 tiller

1
2
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller

套用 tiller 的 ServiceAccount 到已經現有的上

1
kubectl patch deployment --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'

安裝 Helm with RBAC

所有設定一次到位,在 helm init 的時候,加了 –service-account 參數,直接套用 ServiceAccount:

1
2
3
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
helm init --service-account tiller --history-max 200

測試

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ helm install --name my-db stable/influxdb
NAME: my-db
LAST DEPLOYED: Wed Jul 3 23:13:08 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/ConfigMap
NAME DATA AGE
my-db-influxdb 1 1s

==> v1/PersistentVolumeClaim
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-db-influxdb Pending standard 1s

==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
my-db-influxdb-689d74646c-6fwc4 0/1 Pending 0 1s

==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-db-influxdb ClusterIP 10.11.249.197 <none> 8086/TCP,8088/TCP 1s

==> v1beta1/Deployment
NAME READY UP-TO-DATE AVAILABLE AGE
my-db-influxdb 0/1 1 0 1s

...

查看套件清單

1
2
3
$ helm ls
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
my-db 1 Wed Jul 3 23:13:08 2019 DEPLOYED influxdb-1.1.9 1.7.6 default

刪除測試檔案,**–purge** 參數可以徹底刪除,不留下任何記錄,名稱可以透過上面的指令查詢:

1
helm delete --purge my-db

解除安裝 Helm

1
2
3
kubectl -n kube-system delete deployment tiller-deploy
kubectl delete clusterrolebinding tiller
kubectl -n kube-system delete serviceaccount tiller

Reference

在 kubernetes 上建立 influxdb 1.7.7

環境

  • Google Kubernetes Engine
  • Kubernetes 版本為 1.12.8

建立 Secret

Kubernetes secret 可以存放重要訊息,像是使用者密碼之類,不適合暴露在外。

設定以下參數,稍後提供給 influxdb 使用:

  1. INFLUXDB_DATABASE:資料庫名稱
  2. INFLUXDB_HOST:主機名稱
  3. INFLUXDB_USERNAME:使用者名稱
  4. INFLUXDB_PASSWORD:使用者密碼
1
2
3
4
5
kubectl create secret generic influxdb-creds \
--from-literal=INFLUXDB_DATABASE=db \
--from-literal=INFLUXDB_USERNAME=root \
--from-literal=INFLUXDB_PASSWORD=root \
--from-literal=INFLUXDB_HOST=influxdb

輸入以下指令檢查參數是否設定完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ kubectl get secrets
NAME TYPE DATA AGE
influxdb-creds Opaque 4 46m

$ kubectl describe secrets influxdb-creds
Name: influxdb-creds
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
INFLUXDB_DATABASE: 12 bytes
INFLUXDB_HOST: 8 bytes
INFLUXDB_PASSWORD: 4 bytes
INFLUXDB_USERNAME: 4 bytes

密文型態為 Opaque,裡面有四筆資料

建立儲存空間 Persistent Volume Claim (PVC)

使用 Kubernetes PVC 宣告儲存空間,將儲存空間命名為 influxdb,容量大小 2GB:

1
2
3
4
5
6
7
8
9
10
11
12
13
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
app: influxdb
name: influxdb
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi

套用設定:

1
kubectl apply -f pvc.yaml

建立 Deployment

直接使用 Docker Hub 上的 influxdb,版本為 1.7.7

1
kubectl run influx-deployment --image=influxdb:1.7.7

套用 Secret

套用先前設定的 influxdb-creds Secret,輸入以下指令進入修改模式:

1
kubectl edit deployment influxdb

spec.template.spec.containers 位置加入 kubernetes secret:

1
2
3
4
5
6
7
8
spec:
template:
spec:
containers:
- name: influxdb
envFrom:
- secretRef:
name: influxdb-creds

套用 Persistent Volume Claim (PVC)

套用先前設定的 influxdb PVC,輸入以下指令進入修改模式:

1
kubectl edit deployment influxdb

spec.template.spec.volumes 位置加入 Persistent Volume Claim (PVC):

1
2
3
4
5
6
7
spec:
template:
spec:
volumes:
- name: var-lib-influxdb
persistentVolumeClaim:
claimName: influxdb

並且在 spec.template.spec.containers.volumeMounts 位置加入 Persistent Volume (PV):

1
2
3
4
5
6
7
spec:
template:
spec:
containers:
volumeMounts:
- mountPath: /var/lib/influxdb
name: var-lib-influxdb

最後整份文件的結果會長得如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "4"
creationTimestamp: 2019-07-02T15:59:33Z
generation: 4
labels:
run: influxdb
name: influxdb
namespace: default
resourceVersion: "15136"
selfLink: /apis/apps/v1/namespaces/default/deployments/influxdb
uid: 62295880-9ce2-11e9-8f7f-42010a8000e8
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 2
selector:
matchLabels:
run: influxdb
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
run: influxdb
spec:
containers:
- envFrom:
- secretRef:
name: influxdb-creds
image: docker.io/influxdb:1.7.7
imagePullPolicy: IfNotPresent
name: influxdb
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /var/lib/influxdb
name: var-lib-influxdb
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- name: var-lib-influxdb
persistentVolumeClaim:
claimName: influxdb
status:
...

Expose Service

這裡使用 LoadBalancer 來 expose 我們的服務,需要有雲端平台的支援:

1
kubectl expose deployment influx-deployment --name=influx-service --type=LoadBalancer --port=8086 --target-port=8086

EXTERNAL-IP 顯示為 pending,則再過一陣再重輸入一次。

1
2
3
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
influxdb LoadBalancer 10.11.255.248 34.68.51.223 8086:31365/TCP 69m

看到 IP address 就代表完成了,接下來就測試看看是否能連進 server。

測試

從是否能從外部連進來:

1
2
3
4
5
6
7
$ influx --version
InfluxDB shell version: 1.7.7

$ influx -host '34.68.51.223' -port '8086' -username 'root' -password 'root'
Connected to http://34.68.51.223:8086 version 1.7.7
InfluxDB shell version: 1.7.7
>

Reference

物件導向程式設計 object-oriented-programming (OOP)

物件導向程式設計 object-oriented-programming (OOP) 是一種程式語言模型 (programming language model),程式需要圍繞著物件而不是圍繞著 function 和邏輯。

Object-oriented programming (OOP) is a programming language model in which programs are organized around data, or objects, rather than functions and logic. An object can be defined as a data field that has unique attributes and behavior.

當你看到 class 跟 object 可以把它們看成是相同的東西,因為英文通常會寫成**一類物件 (a class of objects)**。

物件裡面可以包含兩個東西:

  • 唯一的屬性 unique attributes
  • 操作屬性的行為 behavior that manipulate it

物件導向裡面,行為又可以稱作為方法 method

定義

物件導向有四個基本的原則,分別是:

封裝 Encapsulation

封裝是一個物件導向的概念,它將屬性 (data) 和操作屬性的行為 (function) 綁在一起,同時避免外部的干擾和誤用。data hiding 能夠把不需要透露給外部的資料隱藏起來,讓外部無法存取。

Encapsulation is an Object Oriented Programming concept that binds together the data and functions that manipulate the data, and that keeps both safe from outside interference and misuse.

抽象 Abstraction

抽象是一種設計 programming 的技術,需要將介面 (interface)實作 (implementation) 的分離。簡單來說就是設計 API 的介面。在 C++ 需要把設計好的介面放在 public 底下。

Abstraction is a programming (and design) technique that relies on the separation of interface and implementation.

繼承 Inheritance

在定義物件時,子類 (subclass) 可以繼承一個或多個物件 classes。

Inheritance is the concept that when a class of objects is defined, any subclass that is defined can inherit the definitions of one or more general classes

多型 Polymorphism

在物件被繼承的時候,允許變數、函數或物件具有多於一種形式。相關的概念有 dynamic binding,在 C++ 裡面需要用到 virtual

Polymorphism is the characteristic of being able to assign a different meaning or usage to something in different contexts - specitically, to allow an entity such as a variable, a function or, an object to have more than one form.

比較 Encapsulation vs Abstraction

看完定義以後,會不會覺得封裝 (Encapsulation) 跟抽象 (Abstraction) 的定義有點類似:

  • 封裝強調的是綑綁 data 的機制,和使用這些 data 的 function

Data encapsulation is a mechanism of bundling the data, and the functions that use them

  • 抽象強調的是暴露介面和隱藏實作細節的機制

Data abstraction is a mechanism of exposing only the interfaces and hiding the implementation details from the user.

Example Code

以下是程式碼範例:

Reference

C++ Friend Class and Function

Friend

將另一個 class 設成 firend,可以讓該 class 存取自己 private data,參考底下的範例:

A 將 B 設成 firend,則物件 B 裡 private、protected、public function 都可以存取該物件 A 裡的 private、protected、public 的資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream> 
class A {
private:
int a;
public:
A() { a=0; }
friend class B; // Friend Class
};

class B {
private:
int b;
public:
void showA(A& x) {
// In B, you can access private member of A by adding friend
std::cout << "A::a = " << x.a;
}
};

int main() {
A a;
B b;
b.showA(a);
return 0;
}

上面的程式碼代表著 A 的 private data 可以給 B 使用。

A 將某個 function 設成 firend,則該 function 都可以存取該物件 A 裡的 private、protected、public 的資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
class A {
int a;
public:
A() {a = 0;}
friend void showA(A&); // global friend function
};

void showA(A& x) {
// In this function, you can access private member of A by adding friend
std::cout << "A::a=" << x.a;
}

int main() {
A a;
showA(a);
return 0;
}

上面的程式碼代表著 A 的 private data 可以給 showA function 使用。

friend function 可以放在 class 的任何地方,不受關鍵字 private、protected、public 的限制。

Reference

在 Ubuntu 18.04 上安裝 Docker CE

安裝環境是在 Ubuntu 18.04 上。

安裝 Docker CE

docker.io 是 docker 的舊版本,如果先前有安裝要移除舊版本:

1
sudo apt-get remove docker docker-engine docker.io containerd runc

安裝相關套件:

1
2
3
4
5
6
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common

匯入 docker apt repository:

1
2
3
4
5
6
7
8
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"

sudo apt-get update

安裝 docker ce:

1
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

建立 daemon:

1
2
3
4
5
6
7
8
9
10
11
12
13
# root
sudo cat > /etc/docker/daemon.json <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF

mkdir -p /etc/systemd/system/docker.service.d

啟用 Docker:

1
2
sudo systemctl start docker.service
sudo systemctl enable docker.service

加入使用者權限,加完後記得要重新開啟終端機:

1
2
sudo groupadd docker
sudo usermod -aG docker $USER

安裝完成

1
docker --version

Reference

在 Ubuntu 上重新安裝 Kubernetes

介紹

Kubeadm 有提供一個指令 reset,不過他只會將有關 Kubernetes 的東西刪除,像是 flannelcni 的網路設定,則必須要手動刪除。

這裡使用的環境是:

  • Ubuntu 18.04
  • Kubernetes 1.14.1
  • Flannel 0.10.0

Problem

要讓問題重現,只需要在你安裝好 Kubernetes Cluster 之後,重設 Kubernetes 就會發生:

1
2
kubeadm reset -f
kubeadm init

這個時候你的 coredns 會一直在 pending 的狀態,而且 nodes 會一直是 NodReady:

1
2
3
4
5
6
7
8
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
akiicat NotReady master 65m v1.14.1

$ kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-fb8b8dccf-2t48c 0/1 Pending 0 42s
coredns-fb8b8dccf-x7f87 0/1 Pending 0 42s

看一下 kubelet 是什麼問題,猜測是之前的 CNI 沒有清除乾淨,而套用到舊的資料

1
2
3
4
$ systemctl status kubelet
...
4月 25 14:01:40 akiicat kubelet[6416]: W0425 14:01:40.779474 6416 cni.go:213] Unable to update cni config: No networks found in /etc/cni/net.d
4月 25 14:01:40 akiicat kubelet[6416]: E0425 14:01:40.901231 6416 kubelet.go:2170] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized

Step by Step

接下來就一步一步的解決這個問題,首先切換成 root 權限:

1
sudo su -

先把 Kubernetes 重設,**-f** 參數代表強制執行 reset,不會跳出提示訊息的確認:

1
kubeadm reset -f

停止 kubeletdocker

1
2
systemctl stop kubelet
systemctl stop docker

完全刪除 cniflannel 的資料:

1
2
3
4
rm -rf /var/lib/cni/
rm -rf /var/lib/kubelet/*
rm -rf /run/flannel
rm -rf /etc/cni/

移除 cniflannel 的網路介面卡:

1
2
3
ifconfig cni0 down
brctl delbr cni0
ifconfig flannel.1 down

重新啟動 docker

1
systemctl start docker

這樣就完成了,最後檢查一下網路介面卡與 IP table 有沒有 flannelcni

1
2
ifconfig
route -n

沒有在這上面就成功了。

後續安裝可以參考我寫的這篇文章:Bare Metal 在 Ubuntu 上安裝 Kubernetes

Summary

最後要輸入指令的時候,需要對 Master 跟 Worker 執行不同的指令,以及在不同的權限下執行:

  • Master:代表主結點。
  • Node:代表 Worker 節點或子結點。

[Master, Node] 不管事 master 跟 worker 都要執行 Kubernetes Reset,在執行時要注意權限是否正確:

1
2
3
4
5
6
7
8
9
10
11
12
# root
kubeadm reset -f
systemctl stop kubelet
systemctl stop docker
rm -rf /var/lib/cni/
rm -rf /var/lib/kubelet/*
rm -rf /run/flannel
rm -rf /etc/cni/
ifconfig cni0 down
brctl delbr cni0
ifconfig flannel.1 down
systemctl start docker

[Master] 安裝 Kubernetes:

1
2
# root
kubeadm init --pod-network-cidr 10.244.0.0/16

注意要切換使用者:

1
2
3
4
5
6
# user
mkdir -p $HOME/.kube
sudo cp -f /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/bc79dd1505b0c8681ece4de4c0d86c5cd2643275/Documentation/kube-flannel.yml

[Node] 加入 worker 節點:

1
2
3
# root
kubeadm join 192.168.0.11:6443 --token 3c564d.6q2we53btzqmf1ew \
--discovery-token-ca-cert-hash sha256:a5480dcd68ec2ff27885932ac80d33aaa0390d295d4834032cc1eb554de3d5d2

Reference

管理多個 Kubernetes Cluster:建立、切換、合併 context

用途

要存取某個 Kubernetes 的 cluster,必須先設定好 Kubernetes 的 context,context 裡面會描述要如何存取到你的 Kubernetes 的 cluster。

當你今天有兩個 Kubernetes 的 cluster 的時候,分別是正式版的 cluster 跟測試用的 cluster,很頻繁在這兩個 cluster 作切換,這時候只需要分別對兩個不同的 context 就可以了,然後利用 kubectl 提供的指令,就能在不同的 cluster 作切換。

雖然目前還用不到兩個 cluster,但我還是建議先設定這個檔案,因為在還沒有設定這個檔案前,你可能會需要 ssh 遠端回你的主機才能使用 kubectl 指令,不過你可以在本地端設定這份檔案,就不需要遠端回你的主機了。

能夠有這些方便的功能,都是建立在你已經在本地端建立好你的設定檔。

Context

在 Kubernetes 裡面,切換不同的 cluster 是以 context 為單位,一個 context 裡面必需要三個元件,分別是 UserServerCertification。這三個東西說起來也很直觀,有個使用者 (User) 必須要有憑證 (Certification) 才能連到某個 Cluster (Server)。

底下是一個 Context 所包含的內容:

1
2
3
4
5
6
7
8
9
                                    +---------------+
+---+ Certification |
+---------+ | +---------------+
+---+ Cluster +---+
+---------+ | +---------+ | +--------+
| Context +---+ +---+ Server |
+---------+ | +------+ +--------+
+---+ User |
+------+

Server 跟 Certification 會一起放在 Cluster 底下,這麼做的好處是,如果今天有兩個不同的使用者,想要連到同一個 Cluster,則不需要重新輸入一次 Certification 跟 Server。

通常設定檔會放在 $HOME/.kube/ 底下,然後檔名命名為 config,不需要副檔名,當你使用 kubectl 指令的時候,預設就會使用這個設定檔。

這裡提供一個設定檔的範例,裡面的架構就如同上面的圖,可以對照著看,我也加了些註解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# $HOME/.kube/config
apiVersion: v1
kind: Config
preferences: {}
current-context: kubernetes-admin@kubernetes # 預設使用哪個 Context

clusters:
- cluster:
certificate-authority: .minikube/ca.crt
server: https://192.168.99.100:8443
name: minikube # Cluster 的名稱,可在 Context 使用
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS...
server: https://17.82.125.39:6443
name: kubernetes

contexts:
- context:
cluster: minikube # 選則某個 Cluseter
user: minikube # 選則某個 User
name: minikube # 這個 Context 的名稱
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
- context:
cluster: kubernetes
user: kubernetes-admin
namespace: kube-system # 指定 namesapce,若不指定預設為 default
name: kubernetes-system@kubernetes

users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0...
client-key-data: LS0tLS1CRUdJTiBSU0EgUF...
- name: minikube # User 的名稱,可在 Context 使用
user:
client-certificate: .minikube/client.crt
client-key: .minikube/client.key

如果路徑正確,檔名正確,設定內容正確,就能看一下我們目前狀態:

1
2
3
4
5
$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* kubernetes-admin@kubernetes kubernetes kubernetes-admin
kubernetes-system@kubernetes kubernetes kubernetes-admin kube-system
minikube minikube minikube

第二欄 NAME 就是 context 的名稱,透過 use-context 指令就能夠在不同的設定檔作切換:

1
kubectl config use-context <context-name>

像是這樣:

1
2
3
4
5
6
7
8
$ kubectl config use-context minikube
Switched to context "minikube".

$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
kubernetes-admin@kubernetes kubernetes kubernetes-admin
kubernetes-system@kubernetes kubernetes kubernetes-admin kube-system
* minikube minikube minikube

路徑

尋找設定檔的優先順序:

  1. 優先使用環境變數 $KUBECONFIG 所設定的路徑
  2. 如果沒有設定 $KUBECONFIG 環境變數,則使用 ${HOME}/.kube/config

假設想要使用其他的設定檔 /tmp/admin.conf,那可以這樣輸入:

1
export KUBECONFIG=/tmp/admin.conf

這看起來並不是非常好用,如果你要切換某個 context 的時候,還需要設定 KUBECONFIG 的路徑,如果能把所有 context 整合在一起就會好用多了。

Merge

Kubernetes 目前並沒有 kubectl config merge 的指令,不過在未來可能會有,有興趣的話可以參考這篇 Github 上的討論

暫時性

目前解決方法是用冒號 : 將不同的檔案串在一起,放在越前面的檔案,順位越高:

1
export KUBECONFIG=${HOME}/.kube/config:/tmp/admin.conf

或是在每次輸入指令前都加上 KUBECONFIG 的設定:

1
KUBECONFIG=${HOME}/.kube/config:/tmp/admin.conf kubectl config get-contexts

永久性

直接寫進 bashrc,每當開啟終端機的時候都會套用環境變數:

1
2
# ~/.bashrc
export KUBECONFIG=${HOME}/.kube/config:/tmp/admin.conf

一勞永逸地做法,把新的檔案跟 ${HOME}/.kube/config 合併:

1
KUBECONFIG=${HOME}/.kube/config:/tmp/admin.conf kubectl config view --flatten > mergedkub && mv mergedkub ${HOME}/.kube/config

另外還有腳本:

1
2
3
4
# ~/.bashrc
function kmerge() {
KUBECONFIG=~/.kube/config:$1 kubectl config view --flatten > ~/.kube/mergedkub && mv ~/.kube/mergedkub ~/.kube/config
}

使用方法:

1
$ kmerge /tmp/admin.conf

Reference

在本地端上建立 Kubernetes Dashboard

Installation

在安裝 Dashboard 之前,請先確認你的 Kubernetes 已經裝好了,至於怎麼安裝 Kubernetes 就自行 Google 拉。

接下來我們要開始安裝 Github Kubernetes Dashboard,官網上其實已經說明的很清楚,不過這裡在記錄一下安裝的過程:

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

沒有錯,這樣就安裝好了,然後看一下正在運行的容器:

1
2
3
4
5
6
7
8
9
10
11
12
$ kubectl get all -n kube-system -l k8s-app=kubernetes-dashboard
NAME READY STATUS RESTARTS AGE
pod/kubernetes-dashboard-5f7b999d65-sswt4 1/1 Running 0 84m

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes-dashboard ClusterIP 10.102.216.229 <none> 443/TCP 84m

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/kubernetes-dashboard 1/1 1 1 84m

NAME DESIRED CURRENT READY AGE
replicaset.apps/kubernetes-dashboard-5f7b999d65 1 1 1 84m

Access

接下來我們要連到 Dashboard,先把 kubectl proxy 打開:

1
kubectl proxy

然後連到下面的網址,就能夠看到 Kubernetes Dashboard:

http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/

到這裡就已經可以看到登入畫面了,不過這裡要注意的是,如果你是使用 ssh 連到你的主機的話,要先在本地端設定好 kubectl,在本地端輸入 kubectl proxy

建立 RBAC

Service Account

建立一個 service 的帳戶

1
2
3
4
5
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kube-system

ClusterRoleBinding

將剛剛建立的 Service Account admin-user 與 ClusterRole 做綁定

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kube-system

這樣就這定好了

Login with Token

生成 admin-user 的 token:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
Name: admin-user-token-ctr9s
Namespace: kube-system
Labels: <none>
Annotations: kubernetes.io/service-account.name: admin-user
kubernetes.io/service-account.uid: b2a411d9-64c9-11e9-a59d-1c872c43607f

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1025 bytes
namespace: 11 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLWN0cjlzIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJiMmE0MTFkOS02NGM5LTExZTktYTU5ZC0xYzg3MmM0MzYwN2YiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.wA-CiA6yqJnxDJgQEX0pes32RGm9I_eh0fOoT6oEuLN849FhuNHPJ-LquEm9TFOVs944NXner_fq8czncrQUSiCmErH5ms6mSmXT8Ql-PjDWUCSpwb6rrDd8GCFSLA4LYZbgNQMiD21q2KY8HN3djW0QK1kcl3Cc19lyeAYMQ6DL6WU1AWzlsZEzAkd_P6r8a6KXrDxZNRimIzl61yioVmLqPGN3imT0u3DUvedbDReWVSTBYvOEExvgchgKKuImr0i1V2m7GbfJ01kDwKcehh78YPPMtXG-wfwVLA9xancHtoP0exPQ15fx9fQvswEoH1Qwz5T4dA2TYXKzHweZvg

最後登入的時,使用 Token 登入,將 Token 複製貼上就能夠登入了。

Trouble Shooting

Address already in use

如果你是背景執行 proxy 指令的話:

1
kubectl proxy &

在這個時候,沒有關掉這個程式,直接關掉終端機,再次執行則會發現 port 已經被佔用了,顯示的錯誤如下:

1
F0422 16:14:59.688258   15922 proxy.go:158] listen tcp 127.0.0.1:8001: bind: address already in use

方法一

如果你執行 proxy 的終端機還開著,可以使用 fg 指令把程式叫回,在按 Ctrl-C 即可:

方法二

萬一你把執行 proxy 的終端機關閉了,則必須先找到執行 proxy 的執行緒 pid:

1
2
3
4
5
6
7
8
$ netstat -tulp | grep kubectl
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 localhost:8001 0.0.0.0:* LISTEN 15908/kubectl
# 或是
$ ps -ef | grep kubectl
akiicat 15908 7633 0 16:14 pts/1 00:00:00 kubectl proxy
akiicat 20117 7633 0 16:23 pts/1 00:00:00 grep --color=auto kubectl

找到 pid,上面的例子的 pid 是 15908,然後程式強制刪除:

1
kill -9 <pid>

Reference