事前準備

  • OS: Ubuntu 20.04

安裝 kubectl

~$ sudo snap install kubectl --classic

安裝 helm

~$ sudo snap install helm --classic

準備一個應用程式

如果已經有現成的應用程式可以使用,就可以直接略過這個段落

最簡單的 Express Hello

~$ mkdir simple-http
~$ cd simple-http
~$ npm init -y
~$ npm install express --save
~$ touch index.js
~$ touch Dockerfile

編輯 index.js

// index.js
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send("Hello World!")
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

編輯 Dockerfile

FROM ubuntu:latest

RUN apt update
RUN apt install -y curl
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt install -y nodejs

COPY . app
WORKDIR app
RUN npm install

EXPOSE 3000

CMD ["node", "index.js"]

建構並推上 Dockerhub

~$ docker build -t floatflower/simple-http:latest . 
~$ docker push floatflower/simple-http:latest

開啟 Linode Kubernetes

進到 Linode Kubernetes 的新增頁面中,輸入 Cluster label 以及將地點選在想要的地方,在這裡我選擇了 Japan 2, JP(選在哪裡都沒有關係),並且將 Kubernetes Version 選為1.17

接著選擇自己想要的 Node Pools 中 想要的 Instance 的配置,因為只是講解過程,因此就選 3 個Linode 2GB。

為了確保 availability,Pool 中最好最少有 3 個 node。

接下來就按下 Create 等待叢集創建完畢。

取得叢集配置文件

待叢集建立完畢之後,畫面上就會出現一個xxx-kubeconfig.yaml的連結,按下下載,並將其複製到到.kube/config文件中。

# .kube/config
apiVersion: v1
kind: Config
preferences: {}

clusters:
- cluster:
    certificate-authority-data: ...
    server: https://459cc53d-e195-439e-ba28-209a0a07ec29.ap-northeast-1.linodelke.net:443
  name: lke10472

users:
- name: lke10472-admin
  user:
    as-user-extra: {}
    token: ...

contexts:
- context:
    cluster: lke10472
    namespace: default
    user: lke10472-admin
  name: lke10472-ctx

current-context: lke10472-ctx

然後試著執行:

~$ kubectl get services
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.128.0.1   <none>        443/TCP   4m53s

到這裡叢集就開啟完畢並且可以開始我們的佈署工作了。

開始佈署

創建一個 Deployment

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-http-deployment # Deployment 的名稱
  labels:
    app: simple-http
spec:
  replicas: 3 # 建立三個 pods
  selector:
    matchLabels:
      app: simple-http # 用於讓 deployment 找到對應的 pods
  template:
    metadata:
      labels:
        app: simple-http
    spec:
      containers:
      - name: simple-http
        image: floatflower/simple-http:latest # 要使用的 docker image
        imagePullPolicy: Always # 每次佈署都從 docker registry 拉取最新的
        ports:
        - containerPort: 3000 # Docker image 暴露出來的 port

佈署

~$ kubectl apply -f deployment.yaml
deployment.apps/simple-http-deployment created

# 透過這個指令可以取得 deployment 的當前狀態
~$ kubectl get deployments                             
# 以下這是我們生成的 deployment 當前的狀態
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
simple-http-deployment   3/3     3            3           2m9s

~$ kubectl get pods                                                                    
# 因為將 replicas 設定為 3,所以這裡會有 3 個對應的 pods 產生
NAME                                      READY   STATUS    RESTARTS   AGE
simple-http-deployment-64bcf9bcbf-665hq   1/1     Running   0          2m31s
simple-http-deployment-64bcf9bcbf-htffg   1/1     Running   0          2m31s
simple-http-deployment-64bcf9bcbf-qjmd2   1/1     Running   0          2m31s

創建一個 Service

接著我們要創建一個以 ClusterIP 的 Service 並透過他暴露為內部服務:

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: simple-http-service
spec:
  ports:
    - port: 3000
      protocol: TCP
      targetPort: 3000
  selector:
    # 我們要將擁有 app=simple-http label 的 pods 作為一個內部服務,
    # 這個值是在我們剛才的 deployment.yaml 中的 template.metadata.labels 已經設定過了,
    # 如果有更改,記得同時更改這裡。
    app: simple-http 

佈署服務

~$ kubectl apply -f service.yaml
NAME                  TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes            ClusterIP   10.128.0.1       <none>        443/TCP    25m
simple-http-service   ClusterIP   10.128.200.114   <none>        3000/TCP   24s

這樣我們就完成了 Service 的建立。

建立 Nginx Ingress Controller

接著我們要透過 Nginx Ingress Controller 將我們的內部服務相網際網路暴露出去

安裝 Nginx Ingress Controller

~$ helm repo add stable https://kubernetes-charts.storage.googleapis.com/                                     
"stable" has been added to your repositories

~$ helm install nginx-ingress stable/nginx-ingress --set controller.publishService.enabled=true               
WARNING: This chart is deprecated
NAME: nginx-ingress
LAST DEPLOYED: Tue Sep 15 17:54:11 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
...

~$ kubectl get services -A -owide
NAMESPACE     NAME                            TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)                      AGE     SELECTOR
default       kubernetes                      ClusterIP      10.128.0.1       <none>           443/TCP                      30m     <none>
# 會看到 nginx-ingress-controller 已經安裝完畢,其中 139.162.95.225 這個群集的對外 IP。
default       nginx-ingress-controller        LoadBalancer   10.128.232.172   139.162.95.225   80:31146/TCP,443:31639/TCP   2m54s   app.kubernetes.io/component=controller,app=nginx-ingress,release=nginx-ingress
default       nginx-ingress-default-backend   ClusterIP      10.128.11.162    <none>           80/TCP                       2m54s   app.kubernetes.io/component=default-backend,app=nginx-ingress,release=nginx-ingress
default       simple-http-service             ClusterIP      10.128.200.114   <none>           3000/TCP                     5m34s   app=simple-http
kube-system   kube-dns                        ClusterIP      10.128.0.10      <none>           53/UDP,53/TCP,9153/TCP       30m     k8s-app=kube-dns

設定 DNS A Record

接著我們到我們的 DNS 服務商的頁面去,將我們的網域對應到指定的 IP,我將我的網域設定為simple-http.floatflower.me

配置 Ingress

# ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    - host: simple-http.floatflower.me # IP 對應的網域
      http:
        paths:
          - backend:
              serviceName: simple-http-service # 要指向的 service,現在指向我們剛才創建的 simple-http-service
              servicePort: 3000

佈署

~$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/my-ingress created

~$ kubectl get ingress
# 完成之後就可以看到指定的 host 以及對外的 IP 了。
NAME         HOSTS                        ADDRESS          PORTS   AGE
my-ingress   simple-http.floatflower.me   139.162.95.225   80      3m18s

接著訪問我們設定的網址,就會看到一個還沒有 https 的成果:

幫網站設定 https

接著最後一步就是將 https 設定起來,在這裡我將使用 sslforfree 來創建證書,但是不管你的證書在哪裡買的都沒有問題,設定方法都一樣,申請 https 證書的方法就不另外提了,總之我們會拿到三個檔案:certificate.crt, ca_bundle.crt以及private.key,首先我們需要先把ca_bunde.crt 的內容複製下來然後貼到certificate.crt的後面:

-----BEGIN CERTIFICATE-----
<CERTIFICATE_CONTENT>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<CA_BUNDLE_CONTENT>
-----END CERTIFICATE-----

接著我們要先將處理過後的certificate.crt以及private.key透過base64編碼:

~$ cat certificate.crt | base64
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk....
~$ cat private.key | base64
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS....

接著將這兩個經過 base64 編碼的檔案放到以下的配置文件中:

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: simple-http-tls
data:
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS...
  tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktL...
type: kubernetes.io/tls

佈署 secret

~$ kubectl apply -f secret.yaml
NAME                                  TYPE                                  DATA   AGE
default-token-2cnpn                   kubernetes.io/service-account-token   3      59m
nginx-ingress-backend-token-pxfkz     kubernetes.io/service-account-token   3      31m
nginx-ingress-token-98925             kubernetes.io/service-account-token   3      31m
sh.helm.release.v1.nginx-ingress.v1   helm.sh/release.v1                    1      31m
# 這裡就是剛才新增的憑證
simple-http-tls                       kubernetes.io/tls                     2      66s

將 secret 加入 ingress.yaml 中

# ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    - host: simple-http.floatflower.me # IP 對應的網域
      http:
        paths:
          - backend:
              serviceName: simple-http-service # 要指向的 service
              servicePort: 3000
  tls:
    - hosts:
        - simple-http.floatflower.me
      secretName: simple-http-tls # 剛才新增的 Secret 名稱

佈署

~$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/my-ingress configured
~$ kubectl get ingress                                                                                        
NAME         HOSTS                        ADDRESS          PORTS     AGE
# 這時候就會發現 my-ingress 中心增了 443 port,他已經開始監聽 http 443 port 了。
my-ingress   simple-http.floatflower.me   139.162.95.225   80, 443   26m

完成之後我們就可以去訪問 https 版本的網站了

做到這裡,就代表已經完成佈署以及設定 https 的步驟了。

學會之後,記得將 Linode Kubernetes 以及相關的 NodeBalancer 和 Linode 關閉喔,不然會不斷計費。