在Kubernetes上部署一个容器化的Go应用的具体操作过程

123 阅读7分钟

在Kubernetes上部署一个容器化的Go应用

Deploying a containerized Go app on Kubernetes

简介

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官方网站上的说明来安装kubectlMinikube

一旦安装完成,输入以下命令,启动一个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

总结

这就是本文的全部内容。一如既往,感谢您的阅读。我希望你觉得这篇文章有用。