跳至主要内容

docker-and-kubernetes

Docker and Kubernetes​

為何使用 Docker​

什麼是 Docker​

Docker Client and Docker Server​

使用 Docker Client​

什麼是 Container​

Namespacing and Control Groups 並不屬於 mac os 或 windows 的,而是屬於 linux 的​

基本指令​

Override Default Command​

Command 能不能使用取決於該 image 的 system 有沒有該程式能夠執行​

查看 containers​

docker ps 列出正在執行的 container docker ps --all 列出包含曾經創建過的 container

Container 的生命週期​

docker run = docker create + docker start -a -a 表示顯示 logs

可以重啟之前的 Container,但不能替換原本的 Command​

移除停止的 Containers​

查看 Container 的 logs​

停止 Container​

SIGTERM = terminate signal stop 會通知程式停止,讓程式能夠根據自己寫的程式碼在自己停止之前執行備份之類的事,而 kill 則是直接強制停止程式,若執行 stop 後 10 秒程式仍未自己停止則會執行 kill 強制停止。

在 Container 執行多個 Command​

在執行中的 Container 執行 Command​

-it = -i -t​

i 表示連接 STDIN t 表示格式化輸出文字

在 Container 呼叫命令提示字元​

離開為 CTRL+D 或輸入 exit

啟動 Container 時開啟命令提示字元​

創建 image​

創建 Dockerfile​

docker build . 產生 image ID

docker run ${imageID} 啟動 container

每次執行一個 instruction 會產生暫存的 image 並刪除上一次暫存的 image,然後每次執行下一個 instruction 會先用上次暫存的 image 啟動 container 再執行​

Rebuild with Cache​

因為 build 會根據是否有 cache 的關係,instruction 的順序會影響找不到之前的 cache 而重新 fetch 建立新的 image 和 container

Tagging an Image​

傳統命名規範

技術上來說 tag 是指後面的 version,前面 dockerID/repo 比較像專案名稱​

若沒指定 version 預設為:latest​

啟動時若不指定版本則會拿最新或 latest 的​

Docker Commit for Windows​

用 Docker Commit 手動產生 image​

可以在執行中的 docker 中產生 image 實務上不推薦使用,用 Dockerfile 就好,方便重啟 image ID 可以不用貼全部

Docker with Node.js​

Port Mapping​

只能在 runtime 指定,不能在 Dockerfile 指定

設定 Working Directory​

最小化 Rebuild 成本​

若沒修改 package.json 在安裝套件階段(RUN npm install)就應該繼續使用原本的 Cache 來產生 image

多個 container​

若 Node 和 Redis 用同一個 Container 在以後擴展時會不方便​

因此將 Redis 分出來有自己的 Container​

Docker Compose​

用 Docker CLI 設定網路那些太麻煩,指令太多,所以通常都用 Docker Compose 設定

為了避免每次啟動都要輸入一堆 Docker CLI,所以才用 Docker Compose,另外也方便一次啟動多個 Container 和自動設定他們之間的連線​

version 為 docker-compose 的格式版本,build 表示使用 Dockerfile 建立 image,-在 yml 中表示陣列​

Difference between service and container in docker compose​

https://stackoverflow.com/questions/35565770/difference-between-service-and-container-in-docker-compose

一個 service 可以由一個或多個 container 執行​

Docker 會將 request 出去的 host 檢查是否為 Docker service name,若符合則 request 到該 service container,Redis 預設 port 為 6379​

Docker Compose Command​

第一次若直接使用 docker-compose run 也會自動 build

停止 docker-compose​

-d 為在背景執行 -d=false: Detached mode: Run container in the background, print new container id

處理 container crash​

模擬 crash

Status codes​

除了 0 以外都是 error code

Restart Policies​

unless-stopped 為總是重啟除非在終端機手動輸入停止 no 必須加雙引號或單引號告訴 yaml 為字串,因為 no 在 yaml 中為 boolean 的 false

docker-compose ps​

類似 docker ps,但必須所在目錄要有 docker-compose.yml 檔案,會根據該檔案列出其中正在執行的 container

開發工作流程​

Docker 的目的​

用 CRA 建立 react 專案並新增 Dockfile.dev 用於 dev 環境,之後的 Dockfile 用於 prod 環境​

指定 Dockfile​

docker build -f ${fileName} .

將不必要的 node_modules 刪除避免 COPY,節省 build 的時間​

Docker Volumes​

將本地的 source code 改為 mapping reference 的方式,避免每次檔案更動都要手動 docker build 和 ducker run​

pwd = present working directory

pwd 在 Windows 的 PowerShell 沒有該指定,可以用 git bash,但仍會有問題,所以改使用${PWD}在 PowerShell 上,並在 package.json 的 scripts 上新增 WATCHPACK_POLLING=true​

可以將 WATCHPACK_POLLING=true 移至 docker-compose.yml(推薦),不影響 local 的指令​

CHOKIDAR_USEPOLLING=true 為舊版 webpack 變量,新版為 WATCHPACK_POLLING=true​

https://github.com/facebook/create-react-app/issues/10253#issuecomment-1127340307

-v /app/node_modules 不加:path 代表不 mapping 到本地的 node_modules,因為本地沒有 node_modules​

用 docker-compose 簡化指令,且不再因為 OS 和 CMD 的關係遇到建立 volumes 的問題($(pwd) / ${PWD}那些)​

因為 build: .會去找 Dockfille 而不是 Dockfile.dev context 為 image 的目標位址,dockerfile 為指定的 Dockerfile 檔案

那 Dockfile 還需要 COPY . .嗎?​

如果用 docker-compose 的確是不必要的,但還是會建議留著,因為有可能之後不再用 docker-compose 改用原本的 docker cli 但忘記補上 volume mounting,或留著當作提醒或參考

test 環境也能 hot reload​

不推薦的方法,要記指令和 container ID

docker-compose 也能使用 ps 拿到 service name 再 exec,exec 預設會自動帶-it​

推薦使用 docker-compose

目前遇到 Windows 無法自動 reload App.test.js 的問題,所以必須透過 docker-compose exec web 或 tests npm run test 的方式進去,每次 App.test.js 檔案有更動再按一次 Enter 更新,即使有加 WATCHPACK_POLLING=true 也沒用​

docker attach 的是 npm process(primary process)而不是 start.js​

也可以只建立 web service 就好,測試時也用 web 環境​

建立 prod service​

Multi-Step Docker Build Process​

as 命名 phase/stage name,每個 FROM 開頭都是一個 phase/stage block,--from=phase 或 stage​

nginx 預設 port 是 80,-d 為在背景執行​

docker build . docker run -d -p 8080:80 ${imageId}

RUN、CMD、ENTRYPOINT 的差別​

為 container 命名​

docker run --name my-name busybox

Travis CI​

因為現在 Travis CI 要收費,可以改用 GitHub Actions

-t 為給 image 命名(docker build),--name 為給 container 命名(docker create/docker run)​

新增.travis.yml​

sudo: required 是為了能夠執行 docker build

script 執行指令若回傳 0 以外的 status code 代表有錯​

npm run test -- --test 產生測試覆蓋率​

為了能夠在 npm run test 後 exit,所以不能使用預設的 dev 環境,否則將永遠不會 exit 並回傳 status code,因此要指定為 CI 環境​

-e 為設定環境變數​

建立 AWS Elastic Beanstalk 並新增 docker-compose-dev.yml 和修改 docker-compose.yml​

Elastic Beanstalk 有偵測流量變大自動幫我們 scale 擴展的優點​

建立 AWS Elastic Beanstalk 也會自動建立 AWS S3​

.travis.yml 新增 deploy config​

region 去 Elastic Beanstalk 查看 URL bucket_name 為 Elastic Beanstalk 的 S3 名稱 bucket_path 為 Elastic Beanstalk 的 S3 專案資料夾位置(第一次 deploy 才會建立)

on 指定 master 這個 branch 才觸發​

GitHub Actions​

目前不需要上傳至 Docker Hub,所以其實不需要 run: docker login 這行​

name: Deploy Frontend
on:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
- run: docker build -t cygnetops/react-test -f Dockerfile.dev .
- run: docker run -e CI=true cygnetops/react-test npm test

- name: Generate deployment package
run: zip -r deploy.zip . -x '*.git*'

- name: Deploy to EB
uses: einaregilsson/beanstalk-deploy@v18
with:
aws_access_key: ${{ secrets.AWS_ACCESS_KEY }}
aws_secret_key: ${{ secrets.AWS_SECRET_KEY }}
application_name: docker-gh
environment_name: Dockergh-env
existing_bucket_name: elasticbeanstalk-us-east-1-923445559289
region: us-east-1
version_label: ${{ github.sha }}
deployment_package: deploy.zip

設定 AWS KEY​

去 IAM 新增 user​

設定 Travis CI 環境變數​

.travis.yml 新增 AWS KEY​

Dockerfile 補上 EXPOSE port​

停止使用 Elastic Beanstalk​

單個 Container 部屬問題​

用 Fib 計算機專案模擬多個 Container 情景​

設定 runtime 環境變數​

runtime 環境變數在 build image 還不會設定,要啟動 container 時才會​

Nginx Path Routing​

新增 nginx default.conf​

/etc/nginx/nginx.conf 會 include /etc/nginx/conf.d/*.conf​

server 屬於關鍵字,所以要將 docker-compose 的 server 也改名​

設定 nginx 監聽 port、proxy_pass 反向代理設定、rewrite path 移除/api​

新增 nginx Dockerfile​

docker-compose 新增 nginx​

如果第一次 docker-compose up --build 啟動可能會有錯誤,某些 container 可能還在 install 所以連線失敗,重新 docker-compose up 就好​

dev websocket 問題​

prod CI​

目的:減少每次部屬時在 EB 上重新 build image 的時間,將提前 build 好的 image 存到 Docker Hub,以後改用其他除了 EB 以外的服務時也方便直接下載使用​

prod 的 client nginx 預設的 80 port 改為 3000​

其實可以只需要一個 nginx 就好,但有可能你的 react files 不是使用 nginx 存放,而是其他簡單的 file system data store,​

新增 client 的 nginx config​

當有聲明監聽其他 port 時,會自動停用預設的 80 port​

index 指定 root 的網站初始頁​

try_files 為抓取資源的優先順序,如果請求是/aaa/bbb,那會先去 aaa 資料夾找有沒有 bbb 這個檔案,沒有就找 aaa/bbb 資料夾有沒有 index.html 或.htm 檔案,沒有就找/index.html​

記得補上 try_files $uri $uri/ /index.html

location、root、alias 的差別​

新增 client 的 Dockerfile,EXPOSE 不再是 80 而是 3000​

先移除 test,因為在測試時我們的後端不會啟動,實務上會用 mockup 資料替代​

Travis prod config​

新增.travis.yml​

docker run lovebuizel/react-test npm test -- --cover 改為 docker run -e CI=true lovebuizel/react-test npm test​

sudo: required
services:
- docker

before_install:
- docker build -t lovebuizel/react-test -f ./client/Dockerfile.dev ./client

script:
- docker run lovebuizel/react-test npm test -- --cover

after_success:
-docker build -t lovebuizel/multi-client ./client
-docker build -t lovebuizel/multi-nginx ./nginx
-docker build -t lovebuizel/multi-server ./server
-docker build -t lovebuizel/multi-worker ./worker

其實也可以在.travis.yml 使用 docker-compose​

透過 Docker CLI 登入並將 build 完的 image 上傳至 Docker Hub​

sudo: required
services:
- docker

before_install:
- docker build -t lovebuizel/react-test -f ./client/Dockerfile.dev ./client

script:
- docker run lovebuizel/react-test npm test -- --cover

after_success: -docker build -t lovebuizel/multi-client ./client
-docker build -t lovebuizel/multi-nginx ./nginx
-docker build -t lovebuizel/multi-server ./server
-docker build -t lovebuizel/multi-worker ./worker
## Log in to the docker CLI
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_ID" --password-stdin
## Take those images and push them to docker hub
- docker push lovebuizel/multi-client
- docker push lovebuizel/multi-nginx
- docker push lovebuizel/multi-server
- docker push lovebuizel/multi-worker

設定 Travis 環境變數​

使用 GitHub Actions 在 prod 環境​

prod deploy in AWS​

之前因為只有一個 Dockerfile,所以 EB 會自動幫我們 build image 和 run image​

但這次有多個資料夾,每個資料夾裡面有各自的 Dockfile,EB 不知道該怎麼處理​

Dockerrun.aws.json 告訴 EB 要去哪裡 pull image 和設定 port mapping 等相關資訊​

docker-compose.yml 和 Dockerrun.aws.json 類似,主要差別為前者如何 build image,後者如何使用 image​

EB 其實不知道如何處理 container,尤其是 multi-container,實際上他會委派 AWS 的另一個服務 Elastic Container Service(ECS)去處理,而 ECS 中的 task definition,用於告訴 ECS 怎麼 run single container,其格式又跟 Dockerrun.aws.json 幾乎一樣,所以可以當作文件參考​

新增 Dockerrun.aws.json​

name 名稱無所謂​

hostname 就跟 docker-compose 的 services 一樣,用於 container 間彼此的連接,所以 server 的 hostname 必須為 api,但如果沒用到其實可以不用指定名稱,例如這裡的 worker 和 nginx​

essential 表示如果這個 container 掛掉是否要將其他 container 也一起關掉,必須至少要有一個為 true,這裡選擇 nginx container,因為網站如果掛掉那其他服務也沒用了​

portMappings 就跟 docker-compose 的 ports 一樣,映射本地與 container 的 port​

新版 EB 平台不再使用 Dockerrun.aws.json,而是 docker-compose.yml​

prod 的 redis 和 postgres 建議使用其他服務,而不是自己建立 container​

選擇其他服務的原因​

以後遷移至 EB 以外的服務方便 自動備份很重要

預設 AWS 的服務不能彼此溝通,必須透過 Virtual Private Cloud(VPC)的 Security Group 設定​

每個 region 都有預設的 VPC​

建立 EB 同時也會建立一個預設的 Security Group 套用上去​

Security Group 也可以設定 inbound、outbound 等規則​

建立一個在相同 Security Group 內 AWS service 可以彼此溝通的規則並套用上去​

建立 AWS RDS​

建立 AWS ElastiCache Redis​

建立 Security Group​

套用 Security Group​

設定 EB 環境變數​

DB 使用者、DB 密碼、DB 名稱來自建立 RDS 時設定的 Redis Endpoint RDS Endpoint

新增 AWS IAM user 並設定 Travis 環境變數​

.travis.yml 新增 deploy script​

分配 Container 記憶體容量​

實際要分配多久記憶體沒有一定標準,要自己研究,這裡全部指定為 128MB​

docker-compose.yml 的參數則為 mem_limit: 128m​

version: "3"
services:
client:
image: "lovebuizel/multi-client-10-14"
mem_limit: 128m
hostname: client
server:
image: "lovebuizel/multi-server-10-14"
mem_limit: 128m
hostname: api
environment:
- REDIS_HOST=$REDIS_HOST
- REDIS_PORT=$REDIS_PORT
- PGUSER=$PGUSER
- PGHOST=$PGHOST
- PGDATABASE=$PGDATABASE
- PGPASSWORD=$PGPASSWORD
- PGPORT=$PGPORT
worker:
image: "lovebuizel/multi-worker-10-14"
mem_limit: 128m
hostname: worker
environment:
- REDIS_HOST=$REDIS_HOST
- REDIS_PORT=$REDIS_PORT
nginx:
image: "lovebuizel/multi-nginx-10-14"
mem_limit: 128m
hostname: nginx
ports:
- "80:80"

若有錯誤可以去 Log 查看​

什麼是 Kubernetes 和為何使用​

方便擴展或是你的應用程式需要 run 不同的 Container​

EB 預設的擴展策略,我們無法控制彼此的 container group,且無法有效地將資源放在需要的地方​

希望變成

Cluster 為 Master(負責控制每個 Node)+一個或多個 Nodes​

Node 可以是 VM 或實體電腦,每個 Node 可以有一個或多個 Container,且 image 可以不同​

minikube 只有在 dev 環境會用到,初學者在 prod 建議使用其他服務方便管理也比較安全​

kubectl 負責管理 Node 和其裡面 Container,minikube 目的就只有在本地建立和執行 Kubernetes Cluster​

安裝 Kubernetes 不像安裝 Docker 一樣會自動幫你安裝其他相關的軟體,所以你還要安裝其他的東西​

macOS 安裝 Docker Desktop 內建的 Kubernetes​

Windows 安裝 Docker Desktop 內建的 Kubernetes​

macOS 安裝 Minikube​

Windows 應該使用 Docker Desktop 內建的 Kubernetes 而不是 Minikube,因為 VM 驅動需要的 virtualization 會和 WSL2 衝突​

macOS 安裝 Minikube​

將之前的 multi-client 專案用 container 跑在本地的 Kubernetes Cluster 上​

錯誤提醒​

新開專案並建立兩個 config file​

k8s 是 Kubernetes 的縮寫,意旨 k 和 s 之間有 8 個字母​

client-pod.yaml​

client-node-port.yaml​

會將 config file 透過 kubectl 轉成 k8s cluster 裡的 object​

object 有各種不同的 type(config file 裡的 kind)負責不同的工作,例如 pod 負責 run container、servicce 負責網路設定​

不同 apiVersion 能使用的 object types 也不同​

Minikube 會在你電腦建立 VM(node)​

必須在 pod 裡面才能執行 container,pod 為一個 container group,可以有一個或多個 container,目的是將高度相關或必須一起執行才有意義的 container 放在一起,例如以下的 support containers​

每個 Node 可以有多個 Pod​

metadata 的 name 主要用於查看 log 時方便辨認的,labels 則為 selector 尋找指定的 service 用的,component: web 也可改叫 tier: frontend,只要 labels 和 selector 一樣就好​

Service 又有四種 subtype,在 prod 不太會用 NodePort,因為不希望 port 為 30000~32767,除非遇到少數情況​

Node 內建 kube-proxy 程式,是 Node 對外唯一的接口,負責決定 request 要傳到哪個 Service,NodePort 負責 expose container 的 port​

port 為其他 pod 之間溝通用(目前可以忽略),targetPort 為 mapping 指定的 pod 與外界的 nodePort(port 只能在 3000032767 之間),若沒指定 nodePort 則會隨機指派 3000032767 之間的 port​

套用 config file​

查看所有 pod 的狀態,預設為 default namespace 的 pod,除非加--all-namespaces,另外查看 namespace 為 kebuctl get ns​

READY 左邊為正在 runnung 的 pod 數量,右邊為總共 pod 需要的數量​

查看所有 service 的狀態​

PORT(S)的第一個為 NodePort Service 的 port,第二個為 nodePort,targetPort 單純不重要所以不顯示​

minikube 建立的 VM 不能使用 localhost 訪問,要用 minikube ip 查詢的 ip,若使用 Docker Desktop 則可以直接使用 localhost​

kube-apiserver 有 4 個程式,目的是負責確保 node 運作正常​

config file 只會傳到 master,master 會自動分配哪些 node 要執行哪些 container,那些 node 的 docker 會去 docker hub 抓 image 存到自己的環境並建立 container​

master 有 responsibility list 可以確認監控到的狀態,若刪掉 container 會自己重啟該 container​

k8s 有兩種開發方法,Imperative 和 Declarative​

Imperative 比較麻煩,一般開發和正式環境都不推薦​

Declarative 只要將目標狀態寫在 config file 重新送給 master 就好​

只要知道有些部落格或文件的教學是用 Imperative,建議改成 Declarative 使用​

更新 pod 使用的 image​

Master 透過 Name 和 Kind 知道要更新原本 Pod 的 image,而不是新建一個 Pod​

kubectl describe 查看詳細狀態​

object name 可以不指定​

更改 port 後重新 apply 卻報錯​

pod config 有些屬性是不能更新的,必須砍掉 pod 重建​

Object Type 由 Pods 改用 Deployment​

Deployment 會監測和自動更新 pod,所以適合 dev 和 prod 環境​

Pod 執行一組 container(container 可以一個或多個),而 Deployment 執行一組相同的 pod(pod 可以一個或多個)​

Pod 因為更新不方便,所以只適合一次性的開發用,但仍然不適合正式環境使用​

透過 Deployment 裡的 Pod Template 建立及更新 Pod​

建立 Deployment config file​

replicas 代表 pod 數量,template 就跟先前的 Pod config 類似,Deployment 並不會直接建立 Pod,而是拿 config 叫 Master 建立,所以還需要 selector matchLabels 跟 Master 說要 handle 那些 Pod,template 的 pod labels 可以有多個,所以可能有 selector matchLabels 不需要 handle 全部 pod 的情況​

kubectl delete 會根據 config file 裡的 kind 和 name 去刪除 pod,會需要可能 10 秒的時間,如同之前刪除 container 一樣​

DESIRED 為需要的 pod 數量、CURRENT 為目前的 pod 數量、當 apply config file 更新時會將全部存在的 pod 設為 out of date,等到 pod 更新或重建才會改成 UP-TO-DATE,AVAILABLE 代表成功啟動 container 或已準備好的 pod 數量​

新版改成 READY​

-o wide 等於--output=wide,用來查看更多的資訊​

Service 讓你不用管因為更新或新建 pod 而改變的 ip 是什麼,自動幫你 mapping​

如何確保 k8s 使用的 image 是最新的​

本地重新 build image 並 push 上 docker hub​

要確保 k8s 使用的 image 是最新的是很難的事情,目前沒有好方法,只能選比較不差的方法(新版有 kubectl rollout restart 可以使用)​

因為 config file 沒改動,apply 會被拒絕,第一個因為有可能刪錯 pod 所以最不推薦,這裡選第三個方法​

新版可以用 kubectl rollout restart -f deployment.yml,會用直接新增 pod 的方式重啟 container,期間舊的繼續運行,待新增完後再刪掉舊的 pod,達到服務不中斷的效果​

kubectl set​

用 Minikube 的每次終端機要使用 docker-client 使令時都要將 docker-server 環境指定為 k8s 的 Node,而不是預設的 local docker for Mac/Windows​

因為 eval $(minikube docker-env)是暫時的,每個終端機視窗都是獨立的​

可以只記 minikube docker-env 就好,會有提示你 eval $(minikube docker-env)​

將 multi-container 專案應用在 k8s 上​

CTRL+C 不等於 docker-compose down,資料仍會保存​

刪除 nginx 並新增 k8s 資料夾和 config file​

什麼是 ClusterIP?和 NodePort 的區別差在 NodePort expose pod 到外網,而 ClusterIP expose pod 到內網,讓 cluster 內其他 pod 彼此可以溝通​

新增 ClusterIP config file,屬性跟 NodePort 類似,但因為不用 expose 外網所以沒有 nodePort 屬性,port 和 targetPort 可以不一樣,但目前沒有必要​

kubectl delete deployment​

kubectl delete service​

kubectl apply -f 資料夾名稱 可以 apply 該資料夾的全部 config file​

新增 multi-server 的 deployment config file​

還缺少 db 和 redis 的連線設定

新增 multi-server 的 ClusterIP config file​

將多個 config file 合併成一個 config file,但不推薦,多個 config file 可以立馬知道有幾個 object,也能更快找到該 object 的 config​

新增 multi-worker 的 Deployment config file​

replicas 設 1 就好之後會擴展出去,這裡不用 expose container 的 port,因為沒有其他 object 會連進來 worker,所以也不需要 ClusterIP​

還缺少 redis 連線設定

新增 redis 的 Deployment 和 ClusterIP config file​

新增 postgres 的 Deployment 和 ClusterIP config file​

replicas 為 1 是防止 DB 同時寫入造成資料不一致的衝突​

Persistent Volume Claim(PVC)​

防止 DB pod 掛掉重建後遺失資料,所以將資料存在 Volume(Persistent Volume,PV),透過 PVC 去設定 config​

Volume 在 Container 和 Kubernetes 為不一樣的東西​

Volume 在 k8s 中為 Pod 層級,如果 pod 內 container 掛掉重啟,volume 資料還會存在,但如果 pod 掛掉的話資料仍會遺失​

所以我們需要的是 Persistent Volume Claim(PVC)和 Persistent Volume(PV),而不是 Volume​

Volume 和 Persistent Volume(PV)的差別,Volume 只有 container 掛掉資料不會遺失,而 PV 不管是 container 還是 pod 掛掉資料都不會遺失​

Persistent Volume Claim(PVC)和 Persistent Volume(PV)的關係​

PVC 就像是一個廣告牌,讓 Pod config 知道能有什麼儲存資源能用,若 k8s 當下有提前建好的資源則稱為靜態供應 PV,需要再建立該資源則稱為動態供應 PV​

比喻 換成 PVC

新增 PVC config file​

accessModes 的種類​

PV 存在哪裡​

預設 PV 位置為 local,但如果 k8s 在其他雲端平台上,則該預設 PV 位置可能為該平台的其他 Service,不過一般來說用預設也沒差,除非有想特別指定的位置​

可以通過設定 PVC config file 的 storageClassName 指定 PV 的位置​

查看 PV 位置​

Pod template 選擇創建時要使用哪個 PVC 作為 volume,並指定 container 要使用哪個 volume​

mountPath 和 subPatj 為選擇該 volume 要存在哪裡,這裡作為 DB back up 用​

kubectl get pv 或 pvc,STATUS Bound 代表正在使用​

設定環境變數​

紅色 host 為該 service 的 metadata name,黃色為普通的變數,密碼因為安全原因不能寫在 config file 裡,要另外設定​

沒有"http://",只是要表示 host 為 url​

Secrets object​

因為安全關係我們不用 config file 的方式去建立 Secrets,而是用 imperative 指令的方式去建,在 dev 和 prod 環境都一樣​

secret 的種類大多是 generic,其他可能會用到的有 docker-registry、tls 等等​

<secret_name>類似於 object 的 metadata name​

--from-literal 表示要儲存的密碼在指令的後面,而不是來自其他檔案​

因為設置了 postgres 的密碼而不再使用預設的,所以 postgres deployment config 要額外 override default password​

postgres deployment config 中 image 為 postgres 時若環境變數有 POSTGRES_PASSWORD(舊版為 PGPASSWORD),postgres 會自動將該變數當成預設密碼​

valueFrom name 為前面的<secret_name>​

env 的 port 號要加單引號或雙引號改成字串,不然會報錯​

NodePort 比較適合 dev,所以 prod 改用 Ingress​

LoadBalancer 是舊的方法,現在建議改用 Ingress​

LoadBalancer 只能導入一組特定的 pod,另外 k8s 也會在背後聯繫 cloud provider 需要建立一個 cluster 外部他們自己設定的 load balancer,並自動設定將流量導入 cluster 裡的 load balancer service​

這裡使用的是 ingress-nginx,而不是 kubernetes-ingress​

不同環境安裝 ingress 的方式可能不同​

Ingress 和 Deployment 類似,都是負責將 current state 轉成 desired state​

Ingress 和 ingress-nginx 有一點點不同​

Ingress-Nginx 在 GC 上其實也會建 Load Balancer,Ingress config 會經由 kubectl 建立一個 Deployment,裡面有 nginx-controller 建立的 nginx pod,並建立一個 Load Balancer 依附在上,另外也會預設建立一個額外的 deployment(裡面有 default-backend pod),用來檢測健康狀態,但在現實中會用 express api server 取代,讓要檢查健康狀態的 request 跑到 multi-server​

為何不用自己客製的 nginx 而是 ingress-nginx​

ingress-nginx 有很多額外好用的功能,例如 Sticky Sessions,他會將訪問不經過 ClusterIP(ClustrtIP 仍存在)直接連到 multi-client pod,且相同用戶的 request 都會連到相同的 server,達到不因為 server 不同而無法辨識 session 的功能​

ingress-nginx 補充資料​

https://www.joyfulbikeshedding.com/blog/2018-03-26-studying-the-kubernetes-ingress-system.html

安裝 ingress-nginx​

https://kubernetes.github.io/ingress-nginx/deploy/#quick-start

新增 Ingress config file​

預設 minikube ip 或 docker desktop localhost 的預設 port 為 80 或 443​

apiVersion: networking.k8s.io/v1
## UPDATE API
kind: Ingress
metadata:
name: ingress-service
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/use-regex: "true"
# ADD ANNOTATION
nginx.ingress.kubernetes.io/rewrite-target: /$1
# UPDATE ANNOTATION
spec:
rules:
- http:
paths:
- path: /?(.*)
# UPDATE PATH
pathType: Prefix
# ADD PATHTYPE
backend:
service:
# UPDATE SERVICE FIELDS
name: client-cluster-ip-service
port:
number: 3000
- path: /api/?(.*)
# UPDATE PATH
pathType: Prefix
# ADD PATHTYPE
backend:
service:
# UPDATE SERVICE FIELDS
name: server-cluster-ip-service
port:
number: 5000

Minikube Dashboard​

在 Dashboard 上更新設定不會儲存​

Docker Desktop 的 Kubernetes Dashboard 需要額外設定​

https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#deploying-the-dashboard-ui

錯誤修正,將 server-deployment 的 image stephengrider/multi-server 改為 cygnetops/multi-server-pgfix-5-11​

將 k8s cluster 上到 prod 正式環境​

為何選擇 Google Cloud 而不是 AWS​

Google Kubernetes Engine(GKE)​

要注意建立 Cluster 後就開始計算付費了​

Travis config file​

去 IAM 建立 Service Account 並下載 private key json​

Ruby Version Fix​

需要 Travis CLI 將 service account file 加密再上傳,但 Travis CLI 需要 ruby 的環境(只有 mac 內建),所以使用 Ruby image 的 Docker 來達成,就不用再 local 處理環境的問題​

--no-rdoc --no-ri 為可選,代表不要安裝 document,讓安裝速度更快,ruby 2.4 版後就不再需要了​

因為需要 build native extensions,所以不能使用 ruby 的 alpine 之類的版本,否則會 build 失敗​

travis login github 改用 Personal Token​

travis login​

travis encrypt-file service-account.json(之前下載的 service account file) -r ${使用者名稱/專案名稱}​

添加加密後提示的指令​

加密完要上傳前記得刪除原始下載的 service account file​

更多 Google Cloud CLI config,project 後面為專案 ID​

之前有提到的錯誤修正​

登入 Docker 並 build image 跑測試​

Custom Deployment Providers​

因為沒有 Travis 內建的 k8s provider,只能用 script 叫他執行自己另外寫的 deploy.sh​

會遇到之前沒有強制更新 latest image 的問題,可以一樣用 kubectl rollout restart deployments/server-deployment 解決,另外覺得將 build image 和 push image 像之前一樣放在 after_success 比較好​

之前的寫法

同之前不會抓最新 image 的問題,可以一樣用 kubectl rollout restart deployments/server-deployment 解決​

![](https://i.imgur.com/z0WfYtw.png

這邊用添加 git commit head 的方法​

用 GIT_SHA 也方便有問題時知道是哪個 commit 的版本,第一個 build(push 也要)有加 tag latest(應該也可不加,因為預設是 latest),tag 和 push 兩個版本同時將 latest 和該 GIT_SHA 更新,好讓之後用 kubectl apply -f k8s 時不用指定 GIT_SHA 也能抓到最新的版本​

k8s pull policy​

https://kubernetes.io/docs/concepts/containers/images/#updating-images

添加 Travis env​

CLOUDSDK_CORE_DISABLE_PROMPTS=1 表示不要提示詢問​

deploy.sh file

docker build -t cygnetops/multi-client-k8s:latest -t cygnetops/multi-client-k8s:$SHA -f ./client/Dockerfile ./client
docker build -t cygnetops/multi-server-k8s-pgfix:latest -t cygnetops/multi-server-k8s-pgfix:$SHA -f ./server/Dockerfile ./server
docker build -t cygnetops/multi-worker-k8s:latest -t cygnetops/multi-worker-k8s:$SHA -f ./worker/Dockerfile ./worker

docker push cygnetops/multi-client-k8s:latest
docker push cygnetops/multi-server-k8s-pgfix:latest
docker push cygnetops/multi-worker-k8s:latest

docker push cygnetops/multi-client-k8s:$SHA
docker push cygnetops/multi-server-k8s-pgfix:$SHA
docker push cygnetops/multi-worker-k8s:$SHA

kubectl apply -f k8s
kubectl set image deployments/server-deployment server=cygnetops/multi-server-k8s-pgfix:$SHA
kubectl set image deployments/client-deployment client=cygnetops/multi-client-k8s:$SHA
kubectl set image deployments/worker-deployment worker=cygnetops/multi-worker-k8s:$SHA

在 Google Console 設定 Google Cloud CLI​

為何需要再設定一次 Google Cloud CLI​

在 Google Console 設定 Secret​

Helm(用來管理 k8s 中的套件)​

安裝 Helm 會安裝 Helm CLI + Tiller Server(用來改變 cluster config)​

Role Based Access Control(RBAC),用於規範誰有權限存取修改 cluster 的 object,防止隨便一個 pod 都有權限修改刪除 cluster​

預設 local 環境不會啟用 RBAC,但 Google Cloud 預設會啟用​

因為 Tiller 要修改 cluster,所以需要額外設定 Tiller 的權限,建立有著 ClusterRoleBinding 的 Service Accounts 並指派給 Tiller,要最保險也可以用 RoleBinding​

Google Cloud 安裝 Helm 之前先設定 RBAC​

用 Helm 安裝 ingress-nginx​

Google Cloud 內建的 Load Balancer​

成功部屬​

使用 HTTPS​

購買 domain 和設定​

安裝 Cert Manager​

https://cert-manager.io/docs/installation/helm/#steps

Cert Manager 就像 pod,而 Certificate 和 Issuer 為 object,Issuer 可以有多個​

Issuer config file​

https://cert-manager.io/docs/configuration/acme/#creating-a-basic-acme-issuer

Certificate config file​

部屬後會看到 certificates​

若看到這邊失敗沒關係 Events 才是重點

Ingress config for HTTPS​

annotations 新增 cert manager 和強制跳轉使用 HTTPS spec 新增 tls rules 新增 host 並複製一份 for www host

清理 local 環境​

local k8s 開發無法像 docker volume 一樣自動即時更新,要重新 rebuild image​

Skaffold 有兩種模式可以達到自動即時更新的效果,第一種 rebuild 比較費時,第二種替換更新的檔案比較快(但要專案本來有 hot reload 的功能,例如這裡的 CRA dev 模式和 nodemon)​

新增 Skaffold config file​

push false 表示不要上傳 image 到 docker hub 或其他 docker repository artifacts 為要管理的 image,sync 就是方法二,指定哪些檔案變動時要更新,除了這些檔案以外變動就會用方法一重新 rebuild

deploy 為要管理的 config file,執行 skaffold dev 時會 apply​

skaffold 關掉時會自動刪除 deploy 中 config file 建立的 pod,如果有要永久儲存資料的 db 或 volume 的話可以不將該 config file 放進去,以免 skaffold 關掉時 delete 該 pod​

artifacts 補上需要管理的 image​