本章介紹在Kubernetes中將儲存整合到容器化微服務的各種方法。 1. 匯入外部服務 2. 在Kubernetes內部運行可靠的機器 3. StatefulSet ## 匯入外部服務 已經有正在運行的資料庫系統,但需要被Kubernetes中的程式所使用 可以使用內建的命名及服務探索功能,Kubernetes中所有程式都可以把資料庫當作在Kubernetes叢集中的服務來使用,可以很輕易地利用Kubernetes服務來更換資料庫。 ex:在開發及線上環境中保持高擬真度 ```yaml # 測試站yaml kind: Service metadata: name: my-database # note 'test' namespace here namespace: test ... # 正式站yaml kind: Service metadata: name: my-database # note 'prod' namespace here namespace: prod ... ``` 內部域名分別連到test/prod的DB my-database.test.svc.cluster.internal my-database.prod.svc.cluster.internal ### Services Without Selectors 因為外部DB沒有label,要將外部資料庫導入到Kubernetes中,必須先建立一個不指定Pod選擇器的service,但引用DNS的名稱 ```yaml # dns-service.yaml kind: Service apiVersion: v1 metadata: name: external-database spec: type: ExternalName externalName: database.company.com ``` 建立一般的Service時,也會建立IP位址,並且DNS服務會配置一個A紀錄指向這個IP 建立ExternalName類型的service時,Kubernetes DNS服務則是配置一個CNAME紀錄,這個紀錄會指向指定的外部名稱 ```yaml # 外部DB沒有DNS kind: Endpoints apiVersion: v1 metadata: name: external-ip-database subsets: - addresses: - ip: 192.168.0.1 ports: - port: 3306 ``` 如果有多組備援IP,可以在addresses陣列中設定。一但端點被配置,附載平衡器將可以開始把流量從service重新指向到IP位址的端點。 * 由於使用者或應用程式會假設端點的位置(ip)永遠是最新的,因此須確保ip不是浮動的,或使用自動化的流程更新端點的紀錄。 ### 外部Service的限制 不會主動進行健康檢查,因此必須承擔提供正確設定給Kubernetes的端點的責任 ## 運行可靠的單一個體 使用Kubernetes的基本資源且不要複製儲存空間,並且只運行單一Pod的資料庫或其他儲存方案。 唯一的犧牲就是升級或機器故障的潛在停機時間。 ### 運行一個MySQL單個體 建立三個基本的物件: * persistent volume:獨立於MySQL應用程式生命週期中的永久磁碟區,這個磁碟區在硬碟上擁有自己的生命週期 * Pod:一個運行MySQL應用程式的Pod * service:該service會將這個Pod開放到叢集中的其他容器中 使用NFS來實現最大可移植性 persistent volume ```yaml # nfs-volume.yaml apiVersion: v1 kind: PersistentVolume metadata: name: database labels: volume: my-volume spec: accessModes: - ReadWriteMany capacity: storage: 1Gi nfs: server: 192.168.0.1 path: "/exports" ``` 這裡定義了1GB儲存空間的NFS persistent volume ``` create this persistent volume as usual with: $ kubectl apply -f nfs-volume.yaml ``` 宣告這個persistent volume then apply this file ```yaml # nfs-volume-claim.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: database spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi selector: matchLabels: volume: my-volume ``` 在selector欄位中,填入之前定義的persistent volume的label 現在已經宣告了磁碟區,可以使用ReplicaSet來構建單個體的Pod ``` yaml # mysql-replicaset.yaml apiVersion: extensions/v1 kind: ReplicaSet metadata: name: mysql # Labels so that we can bind a Service to this Pod labels: app: mysql spec: replicas: 1 selector: matchLabels: app: mysql template: metadata: labels: app: mysql spec: containers: - name: database image: mysql resources: requests: cpu: 1 memory: 2Gi env: - name: MYSQL_ROOT_PASSWORD value: some-password-here livenessProbe: tcpSocket: port: 3306 ports: - containerPort: 3306 volumeMounts: - name: database # /var/lib/mysql is where MySQL stores its databases mountPath: "/var/lib/mysql" volumes: - name: database persistentVolumeClaim: claimName: database ``` 建立ReplicaSet後,他將建立一個運行MySQL的Pod並使用之前建立的永久儲存空間,最後一步,是將其作為service開放出來 ```yaml mysql-service.yaml apiVersion: v1 kind: Service metadata: name: mysql spec: ports: - port: 3306 protocol: TCP # Labels so that we can bind a Service to this Pod selector: app: mysql ``` 現在在叢集中,我們有一個可靠的單一MySQL個體在運行,並且透過service將他以mysql的名字供人使用,可以透過完整的域名mysql.svc.default.cluster來存取他 ### 動態詞碟區擴充 透過動態詞碟區擴充,讓叢集操作人員建立一到多個StorageClass物件 ex:在Azure上利用storage class自動配置硬碟的範例 ```yaml # storageclass.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: default annotations: storageclass.beta.kubernetes.io/is-default-class: "true" labels: kubernetes.io/cluster-service: "true" # azure提供的storage provisioner: kubernetes.io/azure-disk ``` 當叢集建立storage class後,可以在persistent volume claim中引用此儲存類別,而不是引用某個persistent volume ```yaml # dynamic-volume-claim.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: my-claim annotations: volume.beta.kubernetes.io/storage-class: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi ``` volume.beta.kubernetes.io/storage-class: default的annotation表示將此宣告連結到剛剛所建立的儲存類別上 * 自動建立的永久磁碟區的生命週期被磁碟區宣告的回收政策所控制,而預設的設定是綁定到建立他的pod的生命週期,如果剛好刪除了自動建立磁碟區的pod,磁碟區也會跟著被刪除 ## 讓StatefulSet使用Kubernetes的儲存空間 ### StatefulSet的特性 StatefulSet的Pod副本群組類似ReplicaSet * 每個Replica具有永久主機名加上唯一的索引值 * 每個Replica依照低到高的索引建立,並且會等到前一個的Pod正常且可運作,才會建立下一個,而這也是用於擴展Replica * 而刪除Pod時,每個Replica也會按照從高到低的索引依序刪除,這個特性也同樣適用於縮小Replica ### 使用StatefulSet手動複製MongoDB 利用StatefulSet物件建立三個包含MongoDB的Pod副本集合 ```ymal mongo-simple.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: mongo spec: serviceName: "mongo" replicas: 3 selector: matchLabels: app: mongo template: metadata: labels: app: mongo spec: containers: - name: mongodb image: mongo:3.4.24 command: - mongod - --replSet - rs0 ports: - containerPort: 27017 name: peer ``` 與前面介紹的ReplicaSet類似,唯一不同的是apiVersion和kind欄位 建立StatefulSet ``` $ kubectl apply -f mongo-simple.yaml ``` 建立後ReplicaSet和StatefulSet就有明顯差別 ``` $ kubectl get pods NAME READY STATUS RESTARTS AGE mongo-0 1/1 Running 0 1m mongo-1 0/1 ContainerCreating 0 10s ``` 第二個是pod按順序慢慢建立,而不像ReplicaSet一次全部建立 StatefulSet被建立後,還需要建立headless的service來管理StatefulSet的DNS項目 在Kubernetes中,如果service沒有cluster IP位址,就稱為headless service 可以在service規格中,利用cluster IP: None建立headless的service ```yaml mongo-service.yaml apiVersion: v1 kind: Service metadata: name: mongo spec: ports: - port: 27017 name: peer clusterIP: None selector: app: mongo ``` 建立這個service通常會配置四個DNS項目 mongo.default.svc.cluster.local mongo-0.mongo.default.svc.cluster.local mongo-1.mongo and mongo-2.mongo. 但與標準的service不同的是,在這主機名上執行DNS會得到StatefulSet中的所有位址 可以透過在其中一個Mongo的replica中執行指令來查看這些DNS項目 ``` $ kubectl run -it --rm --image busybox busybox ping mongo-1.mongo ``` 接下來將使用這些不重複的pod主機名稱,手動設定Mongo副本配置 選擇mongo-0.mongo作為主要節點,在這個pod中執行mongo指令: ``` $ kubectl exec -it mongo-0 mongo > rs.initiate( { _id: "rs0", members:[ { _id: 0, host: "mongo-0.mongo:27017" } ] }); OK ``` 這指令指定mongodb使用mongo-0.mongo作為主要的副本來初始化ReplicaSet的rs0 * rs0的名稱是可以自行決定的,但需要在mongo.yaml的StatefulSet定義中做出相同的改變 初始化Mongo的ReplicaSet之後,可以透過在mongo-0.mongo Pod的mongo工具中執行以下指令來新增其他的副本: ``` > rs.add("mongo-1.mongo:27017"); > rs.add("mongo-2.mongo:27017"); ``` 使用了replica特定的DNS名稱來將他們新增為Mongo叢集中的replica ### 自動化建立MongoDB叢集 為了自動部署我們會新增額外的容器以執行初始化 在不需額外構建新的Docker映像檔的前提下要配置這個Pod,可以使用ConfigMap將script新增到現有的MongoDB映像檔中,以下可看見新增的容器配置: ```yaml ... initContainers: - name: init-mongo image: mongo:3.4.24 command: - bash - /config/init.sh volumeMounts: - name: config mountPath: /config ... volumes: - name: config configMap: name: "mongo-init" ``` 這個容器正在掛載名為mongo-init的ConfigMap磁碟區 這個ConfigMap包含執行初始化的script,這個script的第一步會確定他是否在mongo-0上執行 如果是的話,他會使用與我們之前執行的同樣指令來建立ReplicaSet,如果是在其他的mongo副本上,他將等到ReplicaSet存在,才會將自己註冊為這個ReplicaSet的成員 ex:有完整的ConfigMap物件定義 ```yaml mongo-configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: mongo-init data: init.sh: | #!/bin/bash # Need to wait for the readiness health check to pass so that the # Mongo names resolve. This is kind of wonky. until ping -c 1 ${HOSTNAME}.mongo; do echo "waiting for DNS (${HOSTNAME}.mongo)..." sleep 2 done until /usr/bin/mongo --eval 'printjson(db.serverStatus())'; do echo "connecting to local mongo..." sleep 2 done echo "connected to local." HOST=mongo-0.mongo:27017 until /usr/bin/mongo --host=${HOST} --eval 'printjson(db.serverStatus())'; do echo "connecting to remote mongo..." sleep 2 done echo "connected to remote." if [[ "${HOSTNAME}" != 'mongo-0' ]]; then until /usr/bin/mongo --host=${HOST} --eval="printjson(rs.status())" \ | grep -v "no replset config has been received"; do echo "waiting for replication set initialization" sleep 2 done echo "adding self to mongo-0" /usr/bin/mongo --host=${HOST} \ --eval="printjson(rs.add('${HOSTNAME}.mongo'))" fi if [[ "${HOSTNAME}" == 'mongo-0' ]]; then echo "initializing replica set" /usr/bin/mongo --eval="printjson(rs.initiate(\ {'_id': 'rs0', 'members': [{'_id': 0, \ 'host': 'mongo-0.mongo:27017'}]}))" fi echo "initialized" ``` * 這個script在初始化叢集後,會永遠地休眠 * pod裡的每個容器必須具有一致的ResrartPolicy * 由於我們希望主節點的mongo容器不要被重新啟動,因此我們也需要讓這個初始化用的容器保持持續進行,否則Kubernetes可能會認為這個Pod不健康 ex:使用ConfigMap的完整StatefulSet ```yaml . mongo.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: mongo spec: serviceName: "mongo" replicas: 3 selector: matchLabels: app: mongo template: metadata: labels: app: mongo spec: containers: - name: mongodb image: mongo:3.4.24 command: - mongod - --replSet - rs0 ports: - containerPort: 27017 name: web # This container initializes the MongoDB server, then sleeps. - name: init-mongo image: mongo:3.4.24 command: - bash - /config/init.sh volumeMounts: - name: config mountPath: /config volumes: - name: config configMap: name: "mongo-init" ``` 可以使用下列指令建立一個Mongo叢集 ``` $ kubectl apply -f mongo-config-map.yaml $ kubectl apply -f mongo-service.yaml $ kubectl apply -f mongo.yaml ``` ### persistent volume 和 StatefulSet 對於持久性儲存空間,你需要將persistent volume掛載到/data/db目錄中 在pod模板中,你需要對其進行更新,並將persistent volume宣告掛載到該目錄中: ```yaml ... volumeMounts: - name: database mountPath: /data/db ``` 雖然這種作法與可靠的單個體類似,但由於StatefulSet有多個Pod,因此不能單純地引用persistent volume claim。你需要新增persistent volume claim模板,將以下內容新增到StatefulSet定義的底下: ```yaml volumeClaimTemplates: - metadata: name: database annotations: volume.alpha.kubernetes.io/storage-class: anything spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 100Gi ``` 當volume claim模板新增至StatefulSet定義後,在每一次建立pod時,StatefulSet控制器會根據此模板建立一個persistent volume claim * 為了讓這些副本式persistent volume正常運作,persistent volume需要設定自動配置或是預先配置persistent volume的集合,以供StatefulSet控制器使用。如果沒有可以被建立的claim,則StatefulSet控制器無法建立對應的Pod ### Readiness探測器 建構MongoDB叢集的最後階段是為Mongo容器新增liveness檢查,用於確認容器是否正常運行 對於liveness檢查,可以利用以下mongo工具新增至StatefulSet物件的Pod模板中: ```yaml ... livenessProbe: exec: command: - /usr/bin/mongo - --eval - db.serverStatus() initialDelaySeconds: 10 timeoutSeconds: 10 ... ```
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up