入門實踐丨如何在K3s上部署Web應用程式

RancherLabs發表於2021-06-22

在本文中,我們將使用Flask和JavaScript編寫的、帶有MongoDB資料庫的TODO應用程式,並學習如何將其部署到Kubernetes上。這篇文章是針對初學者的,如果你之前沒有深度接觸過Kubernetes叢集,也不要擔心!

我們將使用K3s,這是一個輕量級的Kubernetes發行版,非常適合快速入門。但首先讓我們談談我們想要實現的目標。

首先,我將介紹示例應用程式。這其實已經簡化了許多細節,但它說明了常見的用例。然後我們將熟悉瞭解容器化應用程式的過程。在我們繼續之前,我會討論我們如何使用容器來讓我們的開發更加輕鬆,特別是如果我們在一個團隊中工作,或者是當我們在一個新的環境中工作時,希望減輕開發人員的負擔。

一旦我們將應用程式容器化,下一步就是將它們部署到Kubernetes上。雖然我們可以手動建立服務、Ingress和閘道器,但我們可以使用Knative以在任何時候都支援我們的應用程式。

設定應用程式

我們將使用一個簡單的TODO應用程式來演示前端、REST API後端和MongoDB協同工作。這要歸功於Prashant Shahi提出的這個例子。我做了一些小改動,純粹是為了教學的目的:

https://github.com/prashant-shahi

首先,git clone程式碼庫:

git clone https://github.com/benjamintanweihao/Flask-MongoDB-K3s-KNative-TodoApp

接下來,我們將檢查目錄,瞭解情況:


 cd Flask-MongoDB-K3s-KNative-TodoApp
 tree

該資料夾結構是一個典型的Flask應用程式。Entry point是app.py,它還包含REST APIs。Templates資料夾包含了將被渲染成HTML的檔案:

開啟 app.py,我們可以看到所有的主要部分:

├── app.py
├── requirements.txt
├── static
│   ├── assets
│   │   ├── style.css
│   │   ├── twemoji.js
│   │   └── twemoji.min.js
└── templates
    ├── index.html
    └── update.html

從上面的程式碼段,您可以看到應用程式需要MongoDB作為資料庫。使用lists()方法,您可以看到如何定義路由(即@ app.route(“/ list”))、如何從MongoDB獲取資料,以及模板是如何呈現的示例。

mongodb_host = os.environ.get('MONGO_HOST', 'localhost')
mongodb_port = int(os.environ.get('MONGO_PORT', '27017'))
client = MongoClient(mongodb_host, mongodb_port)
db = client.camp2016
todos = db.todo 

app = Flask(__name__)
title = "TODO with Flask"

@app.route("/list")
def lists ():
    #Display the all Tasks
    todos_l = todos.find()
    a1="active"
    return render_template('index.html',a1=a1,todos=todos_l,t=title,h=heading)

if __name__ == "__main__":
    env = os.environ.get('APP_ENV', 'development')
    port = int(os.environ.get('PORT', 5000))
    debug = False if env == 'production' else True
    app.run(host='0.0.0.0', port=port, debug=debug)

這裡需要注意的另一件事是使用了MONGO_HOST和MONGO_PORT的環境變數和Flask相關的環境變數。其中,最重要的是debug。當變數設定為True時,Flask伺服器會在檢測到和發生更改時自動重新載入。這在開發過程中特別方便,也是我們要充分利用的特性。

用Docker容器開發

在處理應用程式時,我曾經花費大量時間設定環境並安裝所有依賴項。在那之後,我可以通過新增新功能來啟動和執行。然而,這僅僅描述了一個理想的場景,對嗎?

你有多少次回到你已經開發的應用程式(比如六個月前),卻發現自己正在慢慢陷入依賴項地獄?依賴項通常是一個靈活的目標,除非您採取措施鎖定物件,否則您的應用程式可能無法正常工作。解決這個問題的方法之一是將所有依賴項打包到Docker容器中。

Docker帶來的另一件特性是自動化。這意味著不再需要複製和貼上命令,也不再需要設定資料庫之類的東西。

Docker化 Flask程式

以下是Dockerfile:

FROM alpine:3.7
COPY . /app
WORKDIR /app

RUN apk add --no-cache bash git nginx uwsgi uwsgi-python py2-pip \
    && pip2 install --upgrade pip \
    && pip2 install -r requirements.txt \
    && rm -rf /var/cache/apk/*

EXPOSE 5000
ENTRYPOINT ["python"]

我們從一個最小的(在大小和功能方面)基礎映象開始。然後,應用程式的內容進入容器中的/app目錄。接下來,我們執行一系列命令來安裝Python、Nginx web server和Flask應用程式的所有需求。這些正是在新系統上設定應用程式所需的步驟。

您可以這樣構建Docker容器:

% docker build -t <yourusername>/todo-app .

你將看到這樣如下輸出:

# ...
Successfully built c650af8b7942
Successfully tagged benjamintanweihao/todo-app:latest

那 MongoDB 呢?

您是否應該經歷為MongoDB建立Dockerfile的相同過程?在此之前,已經有人做過這樣的嘗試,具體演示請檢視案例連結:https://hub.docker.com/_/mongo.不過現在您有兩個容器,其中Flask容器依賴於MongoDB容器。

一種方法是先啟動MongoDB容器,然後啟動Flask容器。但是,假設您想新增快取並決定引入Redis容器。那麼啟動每個容器的過程會很快變枯燥繁瑣。解決方案是Docker Compose,這是一個允許您定義和執行多個Docker容器的工具,正符合我們當前面臨的情況。

Docker Compose

以下是Docker compose檔案,docker-compose.yaml:

services:
  flaskapp:
    build: .
    image: benjamintanweihao/todo-app:latest
    ports:
      - 5000:5000
    container_name: flask-app
    environment:
      - MONGO_HOST=mongo
      - MONGO_PORT=27017
    networks:
      - todo-net
    depends_on:
      - mongo
    volumes:
      - .:/app # <--- 
  mongo:
    image: mvertes/alpine-mongo
    ports:
      - 27017:27017
    networks:
      - todo-net

networks:
  todo-net:
    driver: bridge

即使您不熟悉Docker Compose,這裡的YAML檔案也並不複雜。讓我們看一下重要的部分。

在最開頭,這個檔案定義了由flaskapp和mongo組成的服務,以及指定橋接連線的網路。這將建立一個網路連線,以便服務中定義的容器可以相互通訊。

每個服務都定義映象、埠對映和前面定義的網路。在flaskapp中也定義了環境變數(請檢視app.py,看看它們是否確實是相同的)。

我想提醒您注意flask應用程式中指定的volume。我們在這裡所做的是將主機的當前目錄(應該是包含app.py的專案目錄)對映到容器的/app目錄 我們為什麼要這樣做?回想一下,在Dockerfile中,我們將app複製到/app目錄中,如下所示:

COPY . /app

假設你想對應用程式做一個更改。你不可能輕易改變容器中的app.py。通過對本地目錄的對映,你基本上是在用你目錄中的本地副本覆蓋容器中的app.py。因此,假設Flask應用程式處於除錯模式(如果你在這一點上沒有改變任何東西的話,它就是除錯模式),當你啟動容器並做出改變時,渲染的輸出會反映出這個改變。

但是,重要的是要意識到容器中的app.py仍然是舊版本,您仍然需要記住構建新映象(希望您已將CI/CD設定為自動執行此操作)

讓我們看看這是怎麼回事。執行以下命令:

docker-compose up

接下來你將看到:

Creating network "flask-mongodb-k3s-knative-todoapp_my-net" with driver "bridge"
Creating flask-mongodb-k3s-knative-todoapp_mongo_1 ... done
Creating flask-app                                 ... done
Attaching to flask-mongodb-k3s-knative-todoapp_mongo_1, flask-app
# ... more output truncated
flask-app   |  * Serving Flask app "app" (lazy loading)
flask-app   |  * Environment: production
flask-app   |    WARNING: Do not use the development server in a production environment.
flask-app   |    Use a production WSGI server instead.
flask-app   |  * Debug mode: on
flask-app   |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
flask-app   |  * Restarting with stat
mongo_1     | 2021-05-15T15:41:37.993+0000 I NETWORK  [listener] connection accepted from 172.23.0.1:48844 #2 (2 connections now open)
mongo_1     | 2021-05-15T15:41:37.993+0000 I NETWORK  [conn2] received client metadata from 172.23.0.1:48844 conn2: { driver: { name: "PyMongo", version: "3.11.4" }, os: { type: "Linux", name: "", architecture: "x86_64", version: "5.8.0-53-generic" }, platform: "CPython 2.7.15.final.0" }
flask-app   |  * Debugger is active!
flask-app   |  * Debugger PIN: 183-021-098

現在開始在瀏覽器中訪問:http://localhost:5000

圖片

如果你看到這個,恭喜你!Flask和Mongo在一起正常工作了。您可以隨意使用應用程式來感受它。

現在讓我們對應用程式標題中的app.py做一個小小的改動:

index d322672..1c447ba 100644
--- a/app.py
+++ b/app.py
-heading = "tOdO Reminder"
+heading = "TODO Reminder!!!!!"

儲存檔案並重新載入應用程式:

圖片

完成後,您可以輸入以下命令:

docker-compose down

將應用程式部署到Kubernetes上

截至目前,我們已將我們的應用程式及其支援服務(現在只是MongoDB)容器化。我們如何開始將我們的應用程式部署到Kubernetes?

在此之前,讓我們安裝Kubernetes。為此,我選擇了K3s,因為它是安裝Kubernetes和啟動和執行的最簡單方法。

% curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --no-deploy=traefik"  sh -s -

過一會兒,你就可以安裝 Kubernetes了:

[INFO]  Finding release for channel stable
[INFO]  Using v1.20.6+k3s1 as release
[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.20.6+k3s1/sha256sum-amd64.txt
# truncated ...
[INFO]  systemd: Starting k3s

驗證是否已正確設定K3s:

% kubectl get no
NAME      STATUS   ROLES                  AGE     VERSION
artemis   Ready    control-plane,master   2m53s   v1.20.6+k3s1

MongoDB

有多種方法可以完成這一操作。您可以使用我們建立的映象,MongoDB operator或Helm:

helm install mongodb-release bitnami/mongodb --set architecture=standalone --set auth.enabled=false
 Please be patient while the chart is being deployed 

MongoDB(R) can be accessed on the following DNS name(s) and ports from within your cluster:

    mongodb-release.default.svc.cluster.local

To connect to your database, create a MongoDB(R) client container:

    kubectl run --namespace default mongodb-release-client --rm --tty -i --restart='Never' --env="MONGODB_ROOT_PASSWORD=$MONGODB_ROOT_PASSWORD" --image docker.io/bitnami/mongodb:4.4.6-debian-10-r0 --command -- bash

Then, run the following command:
    mongo admin --host "mongodb-release"

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace default svc/mongodb-release 27017:27017 &
    mongo --host 127.0.0.1

安裝Knative和Istio

在本文中,我們將使用Knative。Knative構建在Kubernetes之上,使得開發人員可以很容易地部署和執行應用程式,而不必知道Kubernetes的很多細節。

Knative由兩部分組成:Serving和Eventing。在本節中,我們將討論Serving部分。使用Knative Serving,您可以在幾秒鐘內建立可彈性伸縮的、安全的和無狀態的服務,這就是我們需要對TODO應用程式做的!在此之前,我們先安裝Knative:

以下說明基於:

https://knative.dev/docs/install/install-serving-with-yaml/

kubectl apply -f https://github.com/knative/serving/releases/download/v0.22.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/v0.22.0/serving-core.yaml
kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.22.0/istio.yaml
kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.22.0/net-istio.yaml

這設定了Knative和istio。你可能想知道為什麼我們需要Istio。原因是Knative需要一個Ingress Controller,使其可以執行流量分發(例如, Todo應用程式的版本1和版本2需要同時執行)和自動HTTP請求重試。

Istio有替代方案嗎?或許可以考慮Gloo(https://docs.solo.io/gloo-edge/master/installation/knative/)。但當前不支援Traefik,這就是為什麼我們在安裝K3s時必須禁用它。由於Istio是預設且最受支援的,我們將使用它。

現在等待所有的knative-serving 的pod執行:

kubectl get pods --namespace knative-serving -w
NAME                                READY   STATUS    RESTARTS   AGE
controller-57956677cf-2rqqd         1/1     Running   0          3m39s
webhook-ff79fddb7-mkcrv             1/1     Running   0          3m39s
autoscaler-75895c6c95-2vv5b         1/1     Running   0          3m39s
activator-799bbf59dc-t6v8k          1/1     Running   0          3m39s
istio-webhook-5f876d5c85-2hnvc      1/1     Running   0          44s
networking-istio-6bbc6b9664-shtd2   1/1     Running   0          44s

設定自定義域

預設情況下,Knative Serving使用example.com作為預設域。如果您按照說明設定了K3s,則應該安裝負載均衡器。這意味著通過一些設定,您可以使用sslip.io之類的DNS服務建立自定義域。

sslip.io是一種服務,當使用帶有嵌入式IP地址的主機名進行查詢時,它會返回該IP地址。例如,192.168.0.1.sslip.io等URL將指向192.168.0.1。這是極好的服務,你不必去買你自己的域名。

繼續並應用以下manifest:

kubectl apply -f https://storage.googleapis.com/knative-nightly/serving/latest/serving-default-domain.yaml

如果您開啟 serving-default-domain. yaml,您需要在 spec 中注意到以下內容:

# other parts truncated     
spec:
    serviceAccountName: controller
    containers:
        - name: default-doma
          image: ko://knative.dev/serving/cmd/default-domain
          args: ["-magic-dns=sslip.io"]

這將啟用您將在下一步中需要使用的DNS。

測試是否一切正常

下載kn二進位制檔案。您可以查閱連結:https://knative.dev/development/client/install-kn/。一定要重新命名二進位制檔案 kn然後把它放在$PATH的某個地方。一旦解決了這個問題,就繼續建立示例Hello World服務。我已經將benjamintanweihao/helloworld python映象推送到Docker Hub:

% kn service create helloworld-python --image=docker.io/benjamintanweihao/helloworld-python --env TARGET="Python Sample v1"

這將產生以下輸出:

Creating service 'helloworld-python' in namespace 'default':

  0.037s The Route is still working to reflect the latest desired specification.
  0.099s Configuration "helloworld-python" is waiting for a Revision to become ready.
 29.277s ...
 29.314s Ingress has not yet been reconciled.
 29.446s Waiting for load balancer to be ready
 29.605s Ready to serve.

Service 'helloworld-python' created to latest revision 'helloworld-python-00001' is available at URL:
http://helloworld-python.default.192.168.86.26.sslip.io

輸入以下程式碼即可列出所有名稱空間中所有已部署的Knative服務:

% kn service  list -A

如果有kubectl,這就變成:

% kubectl get ksvc -A

要刪除服務,只需執行以下操作:

kn service delete helloworld-python # or kubectl delete ksvc helloworld-python

如果您還沒有這樣做,請確保TODO應用程式映象已推送到DockerHub。記住用DockerHub ID替換{username}:

% docker push {username}/todo-app:latest

推送映象後,可以使用kn命令建立TODO服務。記住用DockerHub ID替換{username}:

kn service create todo-app --image=docker.io/{username}/todo-app --env MONGO_HOST="mongodb-release.default.svc.cluster.local" 

如果一切執行順利,你將看到:

Creating service 'todo-app' in namespace 'default':

  0.022s The Route is still working to reflect the latest desired specification.
  0.085s Configuration "todo-app" is waiting for a Revision to become ready.
  4.586s ...
  4.608s Ingress has not yet been reconciled.
  4.675s Waiting for load balancer to be ready
  4.974s Ready to serve.

Service 'todo-app' created to latest revision 'todo-app-00001' is available at URL:
http://todo-app.default.192.168.86.26.sslip.io

現在訪問http://todo-app.default.192.168.86.26.sslip.io (或者在上一個輸出的最後一行的內容)您應該可以看到應用程式!現在我們來看看Knative為你做了什麼。KNative僅需一行命令就可以為您啟動一個服務,並且為您提供了一個可以從叢集訪問的URL。

我對Knative的瞭解僅僅停留於表面,但我希望這個教程可以激勵你更多地瞭解它!當我開始看Knative的時候,我不太明白它做了什麼。希望這個例子能讓我們看到Knative的驚人之處和它的便利性。

結 論

在本文中,我們簡要介紹了使用Python構建的web應用程式並需要MongoDB,並學習瞭如何:

  • 使用Docker容器化TODO應用程式
  • 使用Docker減輕依賴項
  • 使用Docker進行開發
  • 使用Docker Compose打包多個容器
  • 安裝K3s
  • 安裝Knative(Serving)和Istio
  • 使用Helm署MongoDB
  • 使用Knative部署TODO應用程式

雖然將應用程式遷移到 Kubernetes 當然不是一項簡單的任務,但是將應用程式容器化通常會讓你成功一半。當然,本文還有很多東西沒有涉及,比如安全性和可擴充套件性。

K3s 是輕量級的Kubernetes發行版,可以極其輕鬆地使用筆記本/桌上型電腦測試和執行 Kubernetes 工作負載,對個人開發者來說十分友好。同時,也十分適合企業在邊緣端大規模部署叢集。

當我開始研究 Knative 的時候,我並不十分理解它的作用。希望這個例子能夠幫助我們理解 Knative 的魅力及其帶來的便利。實際上,Knative 的亮點之一就是“在幾秒鐘之內就能啟動一個可擴充套件的、安全的、無狀態的服務”。

我將在以後的文章中更多地介紹 Knative,並更深入地探討其核心特徵。我希望你能通過這篇教程,將它們應用到你的應用程式中!

相關文章