在Kubernetes上部署一个容器化的Go应用
简介
Kubernetes是一个开源的容器协调器,由谷歌建立,帮助你在云上运行、管理和扩展容器化应用程序。
它拥有自动化部署、扩展和管理现代应用程序的一切。Kubernetes的一些显著特点是。
- 横向自动扩展
- 服务发现和负载平衡
- 滚动式更新,零停机时间
- 自愈机制(使用健康检查)。
- 秘密和配置管理
所有主要的云供应商(谷歌云、AWS、Azure、DigitalOcean等)都有管理Kubernetes平台。这意味着你可以在不同的云平台之间轻松切换,而不需要对你的架构做任何改变。
在这篇文章中,你将学习如何在Kubernetes上部署、管理和扩展一个简单的Go Web应用。
我们将在使用minikube创建的本地kubernetes集群上部署该应用。Minikube是一个工具,可以让你在本地机器的虚拟机中建立一个单节点的Kubernetes集群。它非常适用于学习和使用Kubernetes。
在Go中构建一个网络应用程序样本
让我们建立一个简单的Go网络应用程序来部署在Kubernetes上。启动你的终端,为项目创建一个新的文件夹。
$ mkdir go-kubernetes
接下来,通过运行以下命令来初始化Go模块
$ cd go-kubernetes
$ go mod init github.com/callicoder/go-kubernetes
# Change the module path as per your Github username
现在,创建一个名为 main.go并复制以下代码
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gorilla/mux"
)
func handler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
name := query.Get("name")
if name == "" {
name = "Guest"
}
log.Printf("Received request for %s\n", name)
w.Write([]byte(fmt.Sprintf("Hello, %s\n", name)))
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func readinessHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func main() {
// Create Server and Route Handlers
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.HandleFunc("/health", healthHandler)
r.HandleFunc("/readiness", readinessHandler)
srv := &http.Server{
Handler: r,
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
// Start Server
go func() {
log.Println("Starting Server")
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}()
// Graceful Shutdown
waitForShutdown(srv)
}
func waitForShutdown(srv *http.Server) {
interruptChan := make(chan os.Signal, 1)
signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
// Block until we receive our signal.
<-interruptChan
// Create a deadline to wait for.
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
srv.Shutdown(ctx)
log.Println("Shutting down")
os.Exit(0)
}
该应用程序使用gorilla mux库进行路由。除了/ 端点,它还有/health 和/readiness 端点。你会在后面的章节中发现这些端点的用途。
现在让我们在本地构建并运行该应用程序。
$ go build
$ ./go-kubernetes
2019/07/27 11:51:58 Starting Server
$ curl localhost:8080?name=Rajeev
Hello, Rajeev
Dockerizing the Go application
为了在Kubernetes上部署我们的应用程序,我们需要首先将其容器化。创建一个名为 Dockerfile的文件,并在Docker文件中添加以下配置。
# Dockerfile References: https://docs.docker.com/engine/reference/builder/
# Start from the latest golang base image
FROM golang:latest as builder
# Add Maintainer Info
LABEL maintainer="Rajeev Singh <rajeevhub@gmail.com>"
# Set the Current Working Directory inside the container
WORKDIR /app
# Copy go mod and sum files
COPY go.mod go.sum ./
# Download all dependancies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download
# Copy the source from the current directory to the Working Directory inside the container
COPY . .
# Build the Go app
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
######## Start a new stage from scratch #######
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy the Pre-built binary file from the previous stage
COPY --from=builder /app/main .
# Expose port 8080 to the outside world
EXPOSE 8080
# Command to run the executable
CMD ["./main"]
构建并推送docker镜像到docker hub
让我们在docker hub上构建并推送Go应用的docker镜像,这样我们以后就可以在Kubernetes上部署应用时使用这个镜像了。
# Build the docker image
$ docker build -t go-kubernetes .
# Tag the image
$ docker tag go-kubernetes callicoder/go-hello-world:1.0.0
# Login to docker with your docker Id
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don\'t have a Docker ID, head over to https://hub.docker.com to create one.
Username (callicoder): callicoder
Password:
Login Succeeded
# Push the image to docker hub
$ docker push callicoder/go-hello-world:1.0.0
创建一个Kubernetes部署
好了!现在让我们为我们的应用程序创建一个Kubernetes部署。部署是指示Kubernetes如何创建和更新应用程序实例的一种声明性方式。一个部署由一组相同的、不可区分的Pod组成。
一个Pod代表一个部署单元,即Kubernetes中的一个应用程序实例,它可能由一个单一的容器或少量紧密耦合的容器组成,并共享资源。
当涉及到管理Pod时,部署将低级别的细节抽象出来,比如Pod在哪个节点上运行。Pod是和节点的寿命联系在一起的。因此,当节点死亡时,Pod也会死亡。部署的工作是确保当前的Pod数量与所需的Pod数量相等。
我们在所谓的清单文件中指定了Pod的数量、在Pod中运行什么容器、如何检查Pod是否健康等细节。这是一个简单的yaml文件,包含了一堆配置,包含了我们应用程序的理想状态。
k8s-deployment.yml
apiVersion: apps/v1
kind: Deployment # Type of Kubernetes resource
metadata:
name: go-hello-world # Name of the Kubernetes resource
spec:
replicas: 3 # Number of pods to run at any given time
selector:
matchLabels:
app: go-hello-world # This deployment applies to any Pods matching the specified label
template: # This deployment will create a set of pods using the configurations in this template
metadata:
labels: # The labels that will be applied to all of the pods in this deployment
app: go-hello-world
spec: # Spec for the container which will run in the Pod
containers:
- name: go-hello-world
image: callicoder/go-hello-world:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080 # Should match the port number that the Go application listens on
livenessProbe: # To check the health of the Pod
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 15
timeoutSeconds: 5
readinessProbe: # To check if the Pod is ready to serve traffic or not
httpGet:
path: /readiness
port: 8080
scheme: HTTP
initialDelaySeconds: 5
timeoutSeconds: 1
我在上述部署清单文件中的每个配置旁边都添加了注释。但我想多谈谈其中的一些。
注意上述文件中的配置replicas: 3 。它指示Kubernetes在任何时候都要运行我们应用程序的3个实例。如果一个实例死亡,Kubernetes会自动启动另一个实例。
我们也来谈谈livenessProbe和readinessProbe。有时,一个pod上的容器可以运行,但容器内的应用程序可能出现故障。例如,如果你的代码被死锁了。
Kubernetes有内置的支持,以确保你的应用程序运行正常,有用户实施的应用程序健康和准备状态检查。
准备度探测表明应用程序何时准备好为流量服务。如果准备度检查失败,那么容器将被标记为未准备好,并将从任何负载均衡器中移除。
有效性探针表明一个容器是活的。如果有效性探测多次失败,那么该容器将被重新启动。
使用Minikube启动本地Kubernetes集群并部署应用程序
你需要安装和设置kubectl(Kubernetes命令行工具)和Minikube才能进一步进行。请按照Kubernetes官方网站上的说明来安装kubectl和Minikube。
一旦安装完成,输入以下命令,启动一个Kubernetes集群。
$ minikube start
现在让我们通过使用kubectl应用部署清单,将我们的应用程序部署到minikube集群上。
$ kubectl apply -f k8s-deployment.yml
deployment.apps/go-hello-world created
这就是了!部署已经创建。你可以像这样获得部署清单。
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
go-hello-world 3/3 3 3 25s
你可以键入下面的命令来获取集群中的pods。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
go-hello-world-69b45499fb-7fh87 1/1 Running 0 37s
go-hello-world-69b45499fb-rt2xj 1/1 Running 0 37s
go-hello-world-69b45499fb-xjmlq 1/1 Running 0 37s
Pods默认分配了一个私有的IP地址,在集群外无法到达。你可以使用kubectl port-forward 命令,将本地端口映射到Pod内部的端口,像这样。
$ kubectl port-forward go-hello-world-69b45499fb-7fh87 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
现在你可以在转发的端口上与Pod进行互动。
$ curl localhost:8080
Hello, Guest
$ curl localhost:8080?name=Rajeev
Hello, Rajeev
你也可以通过输入下面的命令来串联Pod的日志。
$ kubectl logs -f go-hello-world-69b45499fb-7fh87
2019/07/27 06:12:09 Starting Server
2019/07/27 06:15:42 Received request for Guest
2019/07/27 06:16:02 Received request for Rajeev
创建一个Kubernetes服务
port-forward 命令适合于直接测试Pod。但是在生产中,你会希望使用服务来暴露Pod。
Pods可以因为各种原因被重启,比如活度检查、准备度检查失败,或者如果它们运行的节点死亡,它们也会被杀死。
Kubernetes提供了服务作为Pod的稳定端点,而不是依赖Pod的IP地址,因为IP地址会变化。服务所暴露的Pod是基于一组标签的。如果Pods有正确的标签,它们就会被我们的服务自动接收和暴露。
服务提供给pod集的访问级别取决于服务类型,可以是:
- ClusterIP: 仅限内部。
- NodePort。给每个节点一个外部IP,可以从集群外部访问,也可以打开一个端口。在Kubernetes集群的每个节点上运行的kube-proxy组件会监听端口上的传入流量,并以轮流方式将其转发到选定的pod。
- 负载平衡器。添加一个来自云提供商的负载平衡器,将流量从服务转发到其中的节点。
让我们通过创建一个服务来展示我们的Pod。在k8s-deployment.yml 文件中添加以下配置。
apiVersion: v1
kind: Service # Type of kubernetes resource
metadata:
name: go-hello-world-service # Name of the resource
spec:
type: NodePort # A port is opened on each node in your cluster via Kube proxy.
ports: # Take incoming HTTP requests on port 9090 and forward them to the targetPort of 8080
- name: http
port: 9090
targetPort: 8080
selector:
app: go-hello-world # Map any pod with label `app=go-hello-world` to this service
现在让我们通过输入以下命令来应用上述配置。
$ kubectl apply -f k8s-deployment.yml
deployment.apps/go-hello-world unchanged
service/go-hello-world-service created
一个服务被创建,用于公开Pod。你可以像这样获得kubernetes集群中的服务列表。
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
go-hello-world-service NodePort 10.111.51.170 <none> 9090:32550/TCP 35s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13h
键入以下命令以获得minikube集群中服务的URL。
$ minikube service go-hello-world-service --url
http://192.168.99.100:32550
这就是全部!现在你可以与上述URL上的服务进行交互。
$ curl http://192.168.99.100:32550
Hello, Guest
$ curl http://192.168.99.100:32550?name=Rajeev
Hello, Rajeev
扩展Kubernetes部署
你可以通过增加kubernetes部署清单中的复制数量来扩展Pod的数量,并使用kubectl应用这些变化。
你也可以使用kubectl scale 命令来增加Pod的数量。
$ kubectl scale --replicas=4 deployment/go-hello-world
deployment.extensions/go-hello-world scaled
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
go-hello-world-69b45499fb-7fh87 1/1 Running 0 112m
go-hello-world-69b45499fb-hzb6v 1/1 Running 0 10s
go-hello-world-69b45499fb-rt2xj 1/1 Running 0 112m
go-hello-world-69b45499fb-xjmlq 1/1 Running 0 112m
删除Kubernetes资源
删除一个Pod
$ kubectl delete pod go-hello-world-69b45499fb-7fh87
pod "go-hello-world-69b45499fb-7fh87" deleted
删除一个服务
$ kubectl delete service go-hello-world-service
service "go-hello-world-service" deleted
删除一个部署
$ kubectl delete deployment go-hello-world
deployment.extensions "go-hello-world" deleted
停止和删除Minikube集群
停止minikube kubernetes集群
$ minikube stop
删除minikube kubernetes集群
$ minikube delete
总结
这就是本文的全部内容。一如既往,感谢您的阅读。我希望你觉得这篇文章有用。
