Bắt đầu
Trong bài viết này tôi sẽ chỉ cho bạn cách triển khai một ứng dụng Go đơn giản sẽ tiêu thụ một api Anime công cộng có tên là Jikan . Mục tiêu là hướng dẫn bạn cách thiết lập cụm kubernetes trên Google Cloud Platform và triển khai một ứng dụng ở đó, vì vậy tôi sẽ không đi sâu vào chi tiết về cách hoạt động của mã Go.
Những gì bạn cần để làm việc trên đường đi trong bài viết này là như sau:
Chuẩn bị một dự án demo
Như mọi khi mã nguồn có sẵn trên tài khoản github của tôi và bạn có thể lấy nó ở đây . Trước khi chúng ta đi sâu vào triển khai thực tế, chúng ta hãy xem nhanh dự án demo của chúng ta sẽ làm gì?
Dự án demo của chúng tôi được gọi là Jikan , đó là một dịch vụ tìm kiếm anime tiêu thụ API công khai MyAnimeList có tên (bạn đoán nó) Jikan . Để sử dụng dịch vụ, yêu cầu GET đến điểm cuối sau
1 2 | $ curl http://{server}/search?q={title} |
Đây là logic chính của chúng tôi. Về cơ bản, tất cả những gì nó làm là tiêu thụ api Jikan, đáp ứng bản đồ cho loại và trả lại của chúng ta.
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 | <span class="token keyword">type</span> service <span class="token keyword">struct</span> <span class="token punctuation">{</span> http <span class="token operator">*</span> http <span class="token punctuation">.</span> Client <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token punctuation">(</span> s <span class="token operator">*</span> service <span class="token punctuation">)</span> <span class="token function">SearchAnime</span> <span class="token punctuation">(</span> query <span class="token builtin">string</span> <span class="token punctuation">)</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token operator">*</span> jikan <span class="token punctuation">.</span> Anime <span class="token punctuation">,</span> <span class="token builtin">error</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> res <span class="token punctuation">,</span> err <span class="token operator">:=</span> s <span class="token punctuation">.</span> http <span class="token punctuation">.</span> <span class="token function">Get</span> <span class="token punctuation">(</span> fmt <span class="token punctuation">.</span> <span class="token function">Sprintf</span> <span class="token punctuation">(</span> apiURL <span class="token punctuation">,</span> query <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">nil</span> <span class="token punctuation">,</span> err <span class="token punctuation">}</span> <span class="token keyword">defer</span> res <span class="token punctuation">.</span> Body <span class="token punctuation">.</span> <span class="token function">Close</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> buf <span class="token punctuation">,</span> err <span class="token operator">:=</span> ioutil <span class="token punctuation">.</span> <span class="token function">ReadAll</span> <span class="token punctuation">(</span> res <span class="token punctuation">.</span> Body <span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">nil</span> <span class="token punctuation">,</span> err <span class="token punctuation">}</span> <span class="token keyword">var</span> r jikanResult <span class="token keyword">if</span> err <span class="token operator">:=</span> json <span class="token punctuation">.</span> <span class="token function">Unmarshal</span> <span class="token punctuation">(</span> buf <span class="token punctuation">,</span> <span class="token operator">&</span> r <span class="token punctuation">)</span> <span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">nil</span> <span class="token punctuation">,</span> err <span class="token punctuation">}</span> animes <span class="token operator">:=</span> <span class="token function">make</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token operator">*</span> jikan <span class="token punctuation">.</span> Anime <span class="token punctuation">,</span> <span class="token number">1</span> <span class="token punctuation">)</span> <span class="token keyword">for</span> <span class="token boolean">_</span> <span class="token punctuation">,</span> ja <span class="token operator">:=</span> <span class="token keyword">range</span> r <span class="token punctuation">.</span> Results <span class="token punctuation">{</span> status <span class="token operator">:=</span> <span class="token string">"Finished"</span> <span class="token keyword">if</span> ja <span class="token punctuation">.</span> Airing <span class="token punctuation">{</span> status <span class="token operator">=</span> <span class="token string">"Ongoing"</span> <span class="token punctuation">}</span> animes <span class="token operator">=</span> <span class="token function">append</span> <span class="token punctuation">(</span> animes <span class="token punctuation">,</span> <span class="token operator">&</span> jikan <span class="token punctuation">.</span> Anime <span class="token punctuation">{</span> MalURL <span class="token punctuation">:</span> ja <span class="token punctuation">.</span> URL <span class="token punctuation">,</span> ImageURL <span class="token punctuation">:</span> ja <span class="token punctuation">.</span> ImageURL <span class="token punctuation">,</span> Title <span class="token punctuation">:</span> ja <span class="token punctuation">.</span> Title <span class="token punctuation">,</span> ShowType <span class="token punctuation">:</span> ja <span class="token punctuation">.</span> Type <span class="token punctuation">,</span> Status <span class="token punctuation">:</span> status <span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> animes <span class="token punctuation">,</span> <span class="token boolean">nil</span> <span class="token punctuation">}</span> |
Để chạy một dự án địa phương
1 2 | $ go run github.com/PrinceNorin/jikan/cmd/http |
Đóng gói ứng dụng của chúng tôi
Bước đầu tiên để triển khai là đóng gói ứng dụng của chúng tôi vào một thùng chứa. Tôi muốn hình ảnh càng nhỏ càng tốt vì vậy tôi đã chọn đầu như hình ảnh cơ sở. Để tóm tắt những gì đang diễn ra, chúng tôi sử dụng xây dựng nhiều giai đoạn. Giai đoạn đầu tiên là xây dựng ứng dụng Go của chúng tôi thành một tệp nhị phân và sau đó ở giai đoạn cuối cùng sao chép tệp nhị phân đó và thực hiện nó trong một điểm vào.
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 | <span class="token keyword">FROM</span> golang <span class="token punctuation">:</span> 1.13.5 <span class="token punctuation">-</span> alpine3.10 AS builder <span class="token keyword">LABEL</span> builder=true <span class="token keyword">RUN</span> mkdir /user && echo <span class="token string">'nobody:x:65534:65534:nobody:/:'</span> <span class="token punctuation">></span> /user/passwd && echo <span class="token string">'nobody:x:65534:'</span> <span class="token punctuation">></span> /user/group <span class="token keyword">WORKDIR</span> /go/src/jikan <span class="token keyword">ADD</span> go.mod go.sum ./ <span class="token keyword">RUN</span> go mod download <span class="token keyword">ADD</span> . . <span class="token keyword">RUN</span> GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build <span class="token punctuation">-</span> a <span class="token punctuation">-</span> v <span class="token punctuation">-</span> i <span class="token punctuation">-</span> o /go/bin/jikan <span class="token punctuation">-</span> ldflags= <span class="token string">"-s -w -extldflags '-f no-PIC -static'"</span> <span class="token punctuation">-</span> tags <span class="token string">'osusergo netgo static_build'</span> github.com/PrinceNorin/jikan/cmd/http <span class="token keyword">FROM</span> scratch <span class="token keyword">LABEL</span> builder=false <span class="token keyword">WORKDIR</span> /app <span class="token keyword">COPY</span> <span class="token punctuation">-</span> <span class="token punctuation">-</span> from=builder /user/group /user/passwd /etc/ <span class="token keyword">COPY</span> <span class="token punctuation">-</span> <span class="token punctuation">-</span> from=builder /etc/ssl/certs/ca <span class="token punctuation">-</span> certificates.crt /etc/ssl/certs/ <span class="token keyword">COPY</span> <span class="token punctuation">-</span> <span class="token punctuation">-</span> from=builder /go/bin/jikan . <span class="token keyword">USER</span> nobody <span class="token punctuation">:</span> nobody <span class="token keyword">ENTRYPOINT</span> <span class="token punctuation">[</span> <span class="token string">"/app/jikan"</span> <span class="token punctuation">]</span> |
Hình ảnh thu được là ~ 6MB
1 2 3 4 | $ docker build -t norin/jikan:1.0.0 . $ docker images | grep jikan norin/jikan 1.0.0 8cdcc06ede56 About an hour ago 6.04MB |
Bây giờ đẩy hình ảnh để đăng ký docker
1 2 3 | $ docker login # enter credentials $ docker push norin/jikan:1.0.0 |
Xác định triển khai Kubernetes
Sau đây sẽ tạo ra một tên triển khai jicky quản lý 4 bản sao ứng dụng của chúng tôi hiển thị trên cổng container 8080
. Hình ảnh sẽ được lấy từ kho trung tâm norin/jikan:1.0.0
của tôi norin/jikan:1.0.0
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <span class="token key atrule">apiVersion</span> <span class="token punctuation">:</span> apps/v1 <span class="token key atrule">kind</span> <span class="token punctuation">:</span> Deployment <span class="token key atrule">metadata</span> <span class="token punctuation">:</span> <span class="token key atrule">name</span> <span class="token punctuation">:</span> jikan <span class="token key atrule">labels</span> <span class="token punctuation">:</span> <span class="token key atrule">app</span> <span class="token punctuation">:</span> jikan <span class="token key atrule">spec</span> <span class="token punctuation">:</span> <span class="token key atrule">replicas</span> <span class="token punctuation">:</span> <span class="token number">4</span> <span class="token key atrule">selector</span> <span class="token punctuation">:</span> <span class="token key atrule">matchLabels</span> <span class="token punctuation">:</span> <span class="token key atrule">app</span> <span class="token punctuation">:</span> jikan <span class="token key atrule">template</span> <span class="token punctuation">:</span> <span class="token key atrule">metadata</span> <span class="token punctuation">:</span> <span class="token key atrule">labels</span> <span class="token punctuation">:</span> <span class="token key atrule">app</span> <span class="token punctuation">:</span> jikan <span class="token key atrule">spec</span> <span class="token punctuation">:</span> <span class="token key atrule">containers</span> <span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span> <span class="token punctuation">:</span> jikan <span class="token key atrule">image</span> <span class="token punctuation">:</span> norin/jikan <span class="token punctuation">:</span> 1.0.0 <span class="token key atrule">ports</span> <span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">containerPort</span> <span class="token punctuation">:</span> <span class="token number">8080</span> |
Tiếp theo chúng ta cần một cách để truy cập ứng dụng của chúng tôi từ web. Có nhiều cách để lưu trữ cái này nhưng ở đây tôi đã chọn để tạo một LoadBalancer . Nó sẽ phân phối lưu lượng truy cập mà nó nhận được cho dịch vụ được gắn nhãn với app: jikan
và ánh xạ tới cổng 8080.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token key atrule">apiVersion</span> <span class="token punctuation">:</span> v1 <span class="token key atrule">kind</span> <span class="token punctuation">:</span> Service <span class="token key atrule">metadata</span> <span class="token punctuation">:</span> <span class="token key atrule">name</span> <span class="token punctuation">:</span> jikan <span class="token key atrule">annotations</span> <span class="token punctuation">:</span> <span class="token key atrule">service.beta.kubernetes.io/linode-loadbalancer-throttle</span> <span class="token punctuation">:</span> <span class="token string">"100"</span> <span class="token key atrule">service.beta.kubernetes.io/linode-loadbalancer-default-protocol</span> <span class="token punctuation">:</span> <span class="token string">"http"</span> <span class="token key atrule">labels</span> <span class="token punctuation">:</span> <span class="token key atrule">app</span> <span class="token punctuation">:</span> jikan <span class="token key atrule">spec</span> <span class="token punctuation">:</span> <span class="token key atrule">type</span> <span class="token punctuation">:</span> LoadBalancer <span class="token key atrule">ports</span> <span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">port</span> <span class="token punctuation">:</span> <span class="token number">80</span> <span class="token key atrule">targetPort</span> <span class="token punctuation">:</span> <span class="token number">8080</span> <span class="token key atrule">protocol</span> <span class="token punctuation">:</span> TCP <span class="token key atrule">name</span> <span class="token punctuation">:</span> http <span class="token key atrule">selector</span> <span class="token punctuation">:</span> <span class="token key atrule">app</span> <span class="token punctuation">:</span> jikan |
Thiết lập dự án & cụm GCP
Nhiệm vụ tiếp theo là thiết lập dự án & cụm GCP bằng cách chạy các lệnh sau. Lệnh đầu tiên sẽ xác thực và nhận mã thông báo truy cập cần thiết cho các lệnh tiếp theo. Tiếp theo sẽ tạo ra một tên dự án Jikan Anime
với id jikan-anime
. Và cuối cùng chúng tôi tạo ra một cụm tên jikan-anime
ở Singapore. Lệnh cuối cùng chuyển kubernetes bối cảnh hiện tại sang cụm được tạo trước đó của chúng tôi.
1 2 3 4 5 6 | $ gcloud auth login # provide login credentials $ gcloud gcloud projects create jikan-anime --name="Jikan Anime" --labels=type=jikan $ gcloud config set project jikan-anime $ gcloud container clusters create jikan-anime --zone=asia-southeast1-a $ gcloud container clusters get-credentials jikan-anime --zone=asia-southeast1-a |
Triển khai ứng dụng cho GCP
Cuối cùng nhưng không kém phần quan trọng, đã đến lúc triển khai ứng dụng của chúng tôi
1 2 3 4 5 6 | $ kubectl apply -f k8s/deployment.yaml $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE jikan LoadBalancer 10.19.240.141 34.87.106.218 80:30523/TCP 97m kubernetes ClusterIP 10.19.240.1 <none> 443/TCP 115m |
1 2 3 4 5 6 | $ kubectl get pods jikan-978fdb895-4jd47 1/1 Running 0 86m jikan-978fdb895-5bnct 1/1 Running 0 86m jikan-978fdb895-7mztx 1/1 Running 0 86m jikan-978fdb895-znh7s 1/1 Running 0 86m |
Như bạn có thể thấy từ 4 pod đầu ra và 1 loadbalancer được tạo như được định nghĩa trong spec.
Kiểm tra
Cố gắng tìm kiếm tiêu đề anime bằng cách đưa ra yêu cầu NHẬN đến điểm cuối sau. Lưu ý ip bên ngoài từ loadbalancer.
1 2 | $ curl http://34.87.106.218/search?q=seishun |
Sửa lỗi
Có một lỗi trong phiên bản 1.0.0 của mã của chúng tôi, thuộc tính không thoát khỏi chuỗi truy vấn. Hãy sửa lỗi này và triển khai các bản vá để sản xuất.
1 2 3 4 5 | <span class="token keyword">func</span> <span class="token punctuation">(</span> s <span class="token operator">*</span> service <span class="token punctuation">)</span> <span class="token function">SearchAnime</span> <span class="token punctuation">(</span> query <span class="token builtin">string</span> <span class="token punctuation">)</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token operator">*</span> jikan <span class="token punctuation">.</span> Anime <span class="token punctuation">,</span> <span class="token builtin">error</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> res <span class="token punctuation">,</span> err <span class="token operator">:=</span> s <span class="token punctuation">.</span> http <span class="token punctuation">.</span> <span class="token function">Get</span> <span class="token punctuation">(</span> fmt <span class="token punctuation">.</span> <span class="token function">Sprintf</span> <span class="token punctuation">(</span> apiURL <span class="token punctuation">,</span> url <span class="token punctuation">.</span> <span class="token function">QueryEscape</span> <span class="token punctuation">(</span> query <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token comment">// rest of the code</span> <span class="token punctuation">}</span> |
Xây dựng lại hình ảnh
1 2 3 | $ docker build -t norin/jikan:1.0.1 . $ docker push norin/jikan:1.0.1 |
Phát hành bản vá để sản xuất và chú ý phiên bản hình ảnh podall
1 2 3 4 5 6 7 8 9 10 11 12 | $ kubectl set image deployment/jikan jikan=norin/jikan:1.0.1 --record $ kubectl rollout status deployment/jikan $ kubectl get pods NAME READY STATUS RESTARTS AGE jikan-978fdb895-4jd47 1/1 Running 0 97m jikan-978fdb895-5bnct 1/1 Running 0 97m jikan-978fdb895-7mztx 1/1 Running 0 97m jikan-978fdb895-znh7s 1/1 Running 0 97m $ kubectl describe pod jikan-978fdb895-4jd47 | grep Image Image: norin/jikan:1.0.1 Image ID: docker-pullable://norin/ <a href="/cdn-cgi/l/email-protection" class="__cf_email__">[email protected]</a> :a1b48661e08183a0f8bc7a13c88369ff81c49a50d1ee74908c8a13a25c7f1354 |