K8s部署简单的Golang单机集群

in 微语 with 0 comment

本篇文章以Ubuntu-20.04系统为例,主要介绍如何使用K8s部署简单的Golang应用集群,参考了k8s-tutorials

准备工作

安装Docker

下载Docker Engine,根据官方文档进行安装

Set up the repository

  1. Update the apt package index and install packages to allow apt to use a repository over HTTPS:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
  1. Add Docker’s official GPG key:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
  1. Use the following command to set up the repository:
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Install Docker Engine

  1. Update the apt package index:
sudo apt-get update
  1. Install Docker Engine, containerd, and Docker Compose.
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  1. Verify that the Docker Engine installation is successful by running the hello-world image.
sudo docker run hello-world

启动Docker

设置Docker服务自启

systemctl enable docker.service

启动Docker服务

service docker start

创建Docker用户组

如果不是在非root用户下安装Docker,需要执行创建Docker用户组,并把当前用户加入到Docker用户组

sudo usermod -aG docker $USER && newgrp docker

登录Docker账号

后续涉及到镜像的上传和拉取,需要登录DockerHub,可以到DockerHub上注册账号,然后执行以下命令登录

docker login

安装 minikube

参考官方文档进行安装

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube

启动 minikube

注意,在国内安装一定要添加--image-mirror-country='cn'参数,否则会出现下载镜像失败的情况

minikube start --container-runtime=docker  --image-mirror-country='cn' --driver='docker'

启动完成后,执行minikube status查看运行状态:

minikube
type: Control Plane
host: Running      
kubelet: Running   
apiserver: Running
kubeconfig: Configured

minikube 命令速查

设置kubectl别名

minikube kubectl设置为kubectl的别名,这样就可以直接使用kubectl命令了

alias kubectl="minikube kubectl --"

如果要永久生效,可以将上述命令添加到~/.bashrc文件中

vim ~/.bashrc
source ~/.bashrc

K8S

Container 容器

编写Golang Demo和Dockerfile

编写一段Golang Demo代码,用于后续部署到K8s集群中,该代码会在3000端口监听请求,并返回[v1] Hello, Kubernetes!字符串

package main

import (
    "io"
    "net/http"
)

func hello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "[v1] Hello, Kubernetes!")
}

func main() {
    http.HandleFunc("/", hello)
    http.ListenAndServe(":3000", nil)
}

下面是 Go 代码对应的 Dockerfile,简单的方案是直接使用 golang 的 alpine 镜像来打包,但是因为我们后续练习需要频繁的推送镜像到 DockerHub 和拉取镜像到 k8s 集群中,为了优化网络速度,我们选择先在 golang:1.20-buster 中将上述 Go 代码编译成二进制文件,再将二进制文件复制到 base-debian10 镜像中运行 (Dockerfile 不理解没有关系,不影响后续学习)。

# Dockerfile
FROM golang:1.20-buster AS builder
RUN mkdir /src
ADD . /src
WORKDIR /src

RUN go env -w GO111MODULE=auto
RUN go build -o main .

FROM gcr.io/distroless/base-debian10

WORKDIR /

COPY --from=builder /src/main /main
EXPOSE 3000
ENTRYPOINT ["/main"]

构建镜像

docker build . -t naisev/hellok8s:v1

构建完成后,使用以下指令查看镜像列表

docker images
# REPOSITORY        TAG       IMAGE ID       CREATED          SIZE
# naisev/hellok8s   v1        537df71a3b4c   12 minutes ago   25.9MB

运行镜像

docker run -p 3000:3000 --name hellok8s -d naisev/hellok8s:v1

测试是否运行成功,如果返回[v1] Hello, Kubernetes!字符串,则说明运行成功

curl 127.0.0.1:3000

推送镜像

执行如下命令,将镜像推送到DockerHub,其中naisev为自己的用户名(在之前的步骤中必须用docker login登录)

docker push naisev/hellok8s:v1

Pod

Pod 是我们将要创建的第一个 k8s 资源,也是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。
编写hellok8s.yaml文件,创建golang demo的一个pod。

apiVersion: v1
kind: Pod
metadata:
  name: hellok8s
spec:
  containers:
    - name: hellok8s-container
      image: naisev/hellok8s:v1

其中 kind 表示我们要创建的资源是 Pod 类型, metadata.name 表示要创建的 pod 的名字,这个名字需要是唯一的。 spec.containers 表示要运行的容器的名称和镜像名称。镜像默认来源 DockerHub。
执行命令,创建demo的pod。

kubectl apply -f hellok8s.yaml

接着使用kubectl get pods命令,查看pod是否创建成功。
接着执行kubectl port-forward hellok8s 4000:3000将golang-demo的3000端口转发到4000。
使用curl 127.0.0.1:4000命令查看是否返回数据。

kubectl get pods
# NAME       READY   STATUS              RESTARTS   AGE
# hellok8s   0/1     ContainerCreating   0          16s

kubectl port-forward hellok8s 4000:3000
# Forwarding from 127.0.0.1:4000 -> 3000
# Forwarding from [::1]:4000 -> 3000

Pod 命令速查

Deployment

在生产环境中,我们基本上不会直接管理 pod,我们需要 kubernetes 来帮助我们来完成一些自动化操作,例如自动扩容或者自动升级版本。这个时候就需要我们来看 kubernetes 的另外一个资源 deployment,来帮助我们管理 pod。
编写deployment.yaml文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
        - image: naisev/hellok8s:v1
          name: hellok8s-container

其中 kind 表示我们要创建的资源是 deployment 类型, metadata.name 表示要创建的 deployment 的名字,这个名字需要是唯一的。
在 spec 里面表示,首先 replicas 表示的是部署的 pod 副本数量,selector 里面表示的是 deployment 资源和 pod 资源关联的方式,这里表示 deployment 会管理 (selector) 所有 labels=hellok8s 的 pod。
template 的内容是用来定义 pod 资源的,你会发现和 Hellok8s Pod 资源的定义是差不多的,唯一的区别是我们需要加上 metadata.labels 来和上面的 selector.matchLabels 对应起来。来表明 pod 是被 deployment 管理,不用在template 里面加上 metadata.name 是因为 deployment 会自动为我们创建 pod 的唯一name。
接下来输入下面的命令,可以创建 deployment 资源。

kubectl apply -f deployment.yaml

kubectl get deployments
# NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
# hellok8s-deployment   1/1     1            1           5s

kubectl get pods
# NAME                                   READY   STATUS    RESTARTS   AGE
# hellok8s-deployment-76d5758775-qcbfh   1/1     Running   0          50s

kubectl delete pod hellok8s-deployment-76d5758775-qcbfh
# pod "hellok8s-deployment-76d5758775-qcbfh" deleted

kubectl get pods
# NAME                                   READY   STATUS    RESTARTS   AGE
# hellok8s-deployment-76d5758775-2vc96   1/1     Running   0          18s

当执行delete命令删除pod后,会发现deployment会自动创建一个新的pod。
接下来修改deployment.yaml文件,将replicas的值改为3,然后执行kubectl apply -f deployment.yaml命令,可以看到deployment会自动创建3个pod。

kubectl apply -f deployment.yaml
# deployment.apps/hellok8s-deployment configured

kubectl get pods
# NAME                                   READY   STATUS    RESTARTS   AGE
# hellok8s-deployment-76d5758775-2vc96   1/1     Running   0          9m15s
# hellok8s-deployment-76d5758775-5nd2b   1/1     Running   0          25s
# hellok8s-deployment-76d5758775-r6ls5   1/1     Running   0          25s

版本升级

修改golang-demo代码,将字符串改为[v2] Hello, Kubernetes!

package main

import (
    "io"
    "net/http"
)

func hello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "[v2] Hello, Kubernetes!")
}

func main() {
    http.HandleFunc("/", hello)
    http.ListenAndServe(":3000", nil)
}

重新编译打包镜像并上传,标签为v2

docker build . -t naisev/hellok8s:v2
docker push naisev/hellok8s:v2

接着修改deployment.yaml文件,修改imagenaisev/hellok8s:v2

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
        - image: naisev/hellok8s:v2
          name: hellok8s-container

重新应用deployment.yaml文件,curl访问地址可以看到版本升级成功

kubectl apply -f deployment.yaml
# deployment.apps/hellok8s-deployment configured

kubectl get pods
# NAME                                   READY   STATUS    RESTARTS   AGE
# hellok8s-deployment-549df48686-r7hrz   1/1     Running   0          32s
# hellok8s-deployment-549df48686-rpttw   1/1     Running   0          13s
# hellok8s-deployment-549df48686-vpdhk   1/1     Running   0          10s

kubectl port-forward hellok8s-deployment-66799848c4-kpc6q 3000:3000
# Forwarding from 127.0.0.1:3000 -> 3000
# Forwarding from [::1]:3000 -> 3000

curl http://127.0.0.1:3000
# [v2] Hello, Kubernetes!

滚动更新 (Rolling Update)

滚动更新是指在不影响服务的情况下,逐步将旧版本的 Pod 替换为新版本的 Pod。在 Kubernetes 中,滚动更新是通过 Deployment 来实现的。我们只需要修改 Deployment 的 Pod 模板,然后重新 apply 即可。
在 deployment 的资源定义中, spec.strategy.type 有两种选择:

大多数情况下我们会采用滚动更新 (RollingUpdate) 的方式,滚动更新又可以通过 maxSurge 和 maxUnavailable 字段来控制升级 pod 的速率,具体可以详细看官网定义

我们先输入命令回滚版本,然后通过kubectl describe pod查看pod的状态,可以看到旧版本的pod被删除,新版本的pod被创建。

kubectl rollout undo deployment hellok8s-deployment
# deployment.apps/hellok8s-deployment rolled back

kubectl describe pod

curl 127.0.0.1:3000
# [v1] Hello, Kubernetes!

除了上面的命令,还可以用 history 来查看历史版本,--to-revision=2 来回滚到指定版本。

kubectl rollout history deployment hellok8s-deployment
kubectl rollout undo deployment/hellok8s-deployment --to-revision=2

接着设置strategy=rollingUpdate,maxSurge=1,maxUnavailable=1,replicas=3deployment.yaml文件中。这个参数配置意味着最大可能会创建4个pod(replicas+maxSurge),最小会有2个pod存活 (replicas-maxUnavailable)。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s-deployment
spec:
  strategy:
     rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  replicas: 3
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
        - image: naisev/hellok8s:v2
          name: hellok8s-container

重新应用deployment.yaml文件,可以看到pod的滚动更新过程。

kubectl apply -f deployment.yaml

更新过程

存活探针 (livenessProb)

存活探测器来确定什么时候要重启容器。 例如,存活探测器可以探测到应用死锁(应用程序在运行,但是无法继续执行后面的步骤)情况。 重启这种状态下的容器有助于提高应用的可用性,即使其中存在缺陷。-- LivenessProb

修改golang-demo代码,新增一个healthz接口,该接口在程序启动前15s返回200状态码,之后返回500状态码。

package main

import (
    "fmt"
    "net/http"
    "time"
)

func healthz(w http.ResponseWriter, r *http.Request) {
    duration := time.Since(started)
    if duration.Seconds() > 15 {
        w.WriteHeader(500)
        w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
    } else {
        w.WriteHeader(200)
        w.Write([]byte("ok"))
    }
}

var started time.Time

func main() {
    started = time.Now()
    http.HandleFunc("/healthz", healthz)
    http.ListenAndServe(":3000", nil)
}

重新编译打包镜像并上传,标签为liveness

docker build . -t naisev/hellok8s:liveness
docker push naisev/hellok8s:liveness

之后修改deployment.yaml文件,新增livenessProbe,在httpGet中设置pathportperiodSeconds字段指定了kubelet每隔3秒执行一次存活探测。initialDelaySeconds字段告诉kubelet在执行第一次探测前应该等待3秒。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s-deployment
spec:
  strategy:
     rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  replicas: 3
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
        - image: naisev/hellok8s:liveness
          name: hellok8s-container
          livenessProbe:
            httpGet:
              path: /healthz
              port: 3000
            initialDelaySeconds: 3
            periodSeconds: 3

重新应用deployment.yaml文件,可以看到pod不断重启的过程。

kubectl apply -f deployment.yaml

kubectl describe pod hellok8s-deployment-7865bcb96c-52x9s
# ...
# Events:
#   Type     Reason     Age                From               Message
#   ----     ------     ----               ----               -------
#   Normal   Scheduled  45s                default-scheduler  Successfully assigned default/hellok8s-deployment-7865bcb96c-52x9s to minikube
#   Normal   Pulling    44s                kubelet            Pulling image "naisev/hellok8s:liveness"
#   Normal   Pulled     33s                kubelet            Successfully pulled image "naisev/hellok8s:liveness" in 3.1296346s (10.1168537s including waiting)
#   Warning  Unhealthy  12s (x3 over 18s)  kubelet            Liveness probe failed: HTTP probe failed with statuscode: 500
#   Normal   Killing    12s                kubelet            Container hellok8s-container failed liveness probe, will be restarted
#   Normal   Pulled     12s                kubelet            Container image "naisev/hellok8s:liveness" already present on machine
#   Normal   Created    11s (x2 over 33s)  kubelet            Created container hellok8s-container
#   Normal   Started    11s (x2 over 33s)  kubelet            Started container hellok8s-container

就绪探针 (readiness)

就绪探测器可以知道容器何时准备好接受请求流量,当一个 Pod 内的所有容器都就绪时,才能认为该 Pod 就绪。 这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。 若 Pod 尚未就绪,会被从 Service 的负载均衡器中剔除。-- ReadinessProb

首先先把服务回滚到v2版本

kubectl rollout undo deployment hellok8s-deployment --to-revision=2

修改golang-demo代码,将healthz接口的响应状态码改为500

package main

import (
    "net/http"
)

func healthz(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(500)
}

func main() {
    http.HandleFunc("/healthz", healthz)
    http.ListenAndServe(":3000", nil)
}

重新编译打包镜像并上传,标签为bad

docker build . -t naisev/hellok8s:bad
docker push naisev/hellok8s:bad

修改deployment.yaml文件,readinessProbe有很多配置字段,可以使用这些字段精确地控制就绪检测的行为:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s-deployment
spec:
  strategy:
     rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  replicas: 3
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
        - image: naisev/hellok8s:bad
          name: hellok8s-container
          readinessProbe:
            httpGet:
              path: /healthz
              port: 3000
            initialDelaySeconds: 1
            successThreshold: 5

应用deployment.yaml文件,可以看到有两个pod处于ready状态,两个pod处于未ready状态,因为设置了maxUnavailable为1,所以有两个v2版本的应用仍然在正常运行

kubectl apply -f deployment.yaml
# deployment.apps/hellok8s-deployment configured

kubectl get pods
# NAME                                   READY   STATUS              RESTARTS   AGE
# hellok8s-deployment-549df48686-845ch   1/1     Running             0          2m20s
# hellok8s-deployment-549df48686-zjtdr   1/1     Running             0          2m17s
# hellok8s-deployment-b4585d889-lpn7r    0/1     ContainerCreating   0          6s
# hellok8s-deployment-b4585d889-nlpl4    0/1     ContainerCreating   0          6s

Service

k8s 提供了一种名叫Service的资源为pod提供一个稳定的EndpointServie位于pod的前面,负责接收请求并将它们传递给它后面的所有pod。一旦服务中的Pod集合发生更改Endpoints就会被更新,请求的重定向自然也会导向最新的pod。

ClusterIP

我们先来看看Service默认使用的ClusterIP类型,修改golang-demo代码,增加返回hostname功能。

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func hello(w http.ResponseWriter, r *http.Request) {
    host, _ := os.Hostname()
    io.WriteString(w, fmt.Sprintf("[v3] Hello, Kubernetes!, From host: %s", host))
}

func main() {
    http.HandleFunc("/", hello)
    http.ListenAndServe(":3000", nil)
}

重新编译打包镜像并上传,标签为v3

docker build . -t naisev/hellok8s:v3
docker push naisev/hellok8s:v3

修改deployment.yaml文件,将镜像标签改为v3,并应用更新

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s-deployment
spec:
  strategy:
     rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  replicas: 3
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
        - image: naisev/hellok8s:v3
          name: hellok8s-container
kubectl apply -f deployment.yaml

接下来创建service-hellok8s-clusterip.yaml文件,我们使用ClusterIP的方式定义Service,通过kubernetes集群的内部IP暴露服务,当我们只需要让集群中运行的其他应用程序访问我们的pod时,就可以使用这种类型的Service。

apiVersion: v1
kind: Service
metadata:
  name: service-hellok8s-clusterip
spec:
  type: ClusterIP
  selector:
    app: hellok8s
  ports:
  - port: 3000
    targetPort: 3000

应用service-hellok8s-clusterip.yaml文件,首先通过 kubectl get endpoints 来看看 Endpoint。被 selector 选中的 Pod,就称为 Service 的 Endpoints。它维护着 Pod 的 IP 地址,只要服务中的 Pod 集合发生更改,Endpoints 就会被更新。通过 kubectl get pod -o wide 命令获取 Pod 更多的信息,可以看到 3 个 Pod 的 IP 地址和 Endpoints 中是保持一致的。

kubectl apply -f service-hellok8s-clusterip.yaml

kubectl get endpoints
# NAME                         ENDPOINTS                                            AGE
# kubernetes                   192.168.49.2:8443                                    46h
# service-hellok8s-clusterip   10.244.0.36:3000,10.244.0.37:3000,10.244.0.38:3000   22m

kubectl get pod -o wide
# NAME                                   READY   STATUS    RESTARTS   AGE     IP            NODE       NOMINATED NODE   READINESS GATES
# hellok8s-deployment-6d468456c7-7qjgv   1/1     Running   0          141m    10.244.0.36   minikube   <none>           <none>
# hellok8s-deployment-6d468456c7-8pgd5   1/1     Running   0          141m    10.244.0.38   minikube   <none>           <none>
# hellok8s-deployment-6d468456c7-qgvrv   1/1     Running   0          141m    10.244.0.37   minikube   <none>           <none>
# nginx                                  1/1     Running   0          7m11s   10.244.0.39   minikube   <none>           <none>

kubectl get service
# NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
# kubernetes                   ClusterIP   10.96.0.1       <none>        443/TCP    46h
# service-hellok8s-clusterip   ClusterIP   10.109.20.231   <none>        3000/TCP   29m

我们可以在集群中创建一个nginx服务,在nginx服务中访问hellok8s:v3服务。创建nginx.yaml文件,创建nginx服务,进入nginx容器后,通过curl命令访问service-hellok8s-clusterip,可以发现每次访问的pod都不一样,可以验证Service实现了负载均衡

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
    - name: nginx-container
      image: nginx
kubectl apply -f nginx.yaml

kubectl exec -it nginx -- /bin/bash

curl 10.109.20.231:3000
# [v3] Hello, Kubernetes!, From host: hellok8s-deployment-6d468456c7-8pgd5root

curl 10.109.20.231:3000
# [v3] Hello, Kubernetes!, From host: hellok8s-deployment-6d468456c7-7qjgvroot

除了上述的 ClusterIp 的方式外,Kubernetes ServiceTypes 允许指定你所需要的 Service 类型,默认是 ClusterIP。Type 的值包括如下:

NodePort

NodePort通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>,你可以从集群的外部访问一个 NodePort 服务。
接着以 NodePort 的 ServiceType 创建一个 Service 来接管 pod 流量。NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过curl请求,可以从集群的外部访问一个 NodePort 服务,最终重定向到 hellok8s:v3 的 3000 端口。
这里需要注意一个坑点,如果本地使用Docker(--driver=docker)的话,NodePort类型的Service、Ingress组件都无法通过minikube ip提供的ip地址来访问。这里提供一个解决方案:进入minikube的docker容器,再执行curl命令。

apiVersion: v1
kind: Service
metadata:
  name: service-hellok8s-nodeport
spec:
  type: NodePort
  selector:
    app: hellok8s
  ports:
  - port: 3000
    nodePort: 30000
kubectl apply -f service-hellok8s-nodeport.yaml
# service/service-hellok8s-nodeport created

kubectl get service
# NAME                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
# kubernetes                   ClusterIP   10.96.0.1        <none>        443/TCP          2d21h
# service-hellok8s-clusterip   ClusterIP   10.109.20.231    <none>        3000/TCP         23h
# service-hellok8s-nodeport    NodePort    10.103.230.213   <none>        3000:30000/TCP   13s

docker exec -it minikube /bin/bash

curl http://127.0.0.1:30000
# [v3] Hello, Kubernetes!, From host: hellok8s-deployment-6d468456c7-7qjgvroot

LoadBalancer

本部分尚未实践

LoadBalancer 是使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上,假如你在 AWS 的 EKS 集群上创建一个 Type 为 LoadBalancer 的 Service。它会自动创建一个 ELB (Elastic Load Balancer) ,并可以根据配置的 IP 池中自动分配一个独立的 IP 地址,可以供外部访问。

这里因为我们使用的是 minikube,可以使用 minikube tunnel 来辅助创建 LoadBalancer 的 EXTERNAL_IP,具体教程可以查看官网文档,但是和实际云提供商的 LoadBalancer 还是有本质区别,所以 Repository 不做更多阐述,有条件的可以使用 AWS 的 EKS 集群上创建一个 ELB (Elastic Load Balancer) 试试。

ingress

Ingress可以“简单理解”为服务的网关 Gateway,它是所有流量的入口,经过配置的路由规则,将流量重定向到后端的服务。
开启Ingress-Controller的功能,并删除之前创建的所有service、deployment。

minikube addons enable ingress

kubectl delete deployment,service --all

创建hellok8s的deployment和service(hellok8s.yaml),service类型为ClusterIp。然后创建nginx的depoyment和service(nginx.yaml),service类型为ClusterIp。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellok8s-deployment
spec:
  strategy:
     rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  replicas: 3
  selector:
    matchLabels:
      app: hellok8s
  template:
    metadata:
      labels:
        app: hellok8s
    spec:
      containers:
        - image: naisev/hellok8s:v3
          name: hellok8s-container
---
apiVersion: v1
kind: Service
metadata:
  name: service-hellok8s-clusterip
spec:
  type: ClusterIP
  selector:
    app: hellok8s
  ports:
  - port: 3000
    targetPort: 3000
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx-container
---
apiVersion: v1
kind: Service
metadata:
  name: service-nginx-clusterip
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
  - port: 4000
    targetPort: 80
kubectl apply -f hellok8s.yaml 
# deployment.apps/hellok8s-deployment created
# service/service-hellok8s-clusterip created

kubectl apply -f nginx.yaml 
# deployment.apps/nginx-deployment created
# service/service-nginx-clusterip created

kubectl get pods
# NAME                                   READY   STATUS    RESTARTS   AGE
# hellok8s-deployment-6d468456c7-l5wn6   1/1     Running   0          2m33s
# hellok8s-deployment-6d468456c7-xsr6t   1/1     Running   0          2m33s
# hellok8s-deployment-6d468456c7-zpvkz   1/1     Running   0          2m33s
# nginx-deployment-7cd8f7f889-lwrqr      1/1     Running   0          2m25s
# nginx-deployment-7cd8f7f889-qvj5g      1/1     Running   0          2m25s

kubectl get service
# NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
# kubernetes                   ClusterIP   10.96.0.1       <none>        443/TCP    72m
# service-hellok8s-clusterip   ClusterIP   10.103.3.195    <none>        3000/TCP   2m50s
# service-nginx-clusterip      ClusterIP   10.99.214.208   <none>        4000/TCP   2m42s

此时,k8s集群中有3个hellok8s:v3的pods,2个nginx的pods,这时候编写ingress.yaml文件,其中nginx.ingress.kubernetes.io/ssl-redirect设为false,表示关闭https。
spec.rules.http.paths中把/hello路径重定向到hellok8s服务,把/路径重定向到nginx服务。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
    - http:
        paths:
          - path: /hello
            pathType: Prefix
            backend:
              service:
                name: service-hellok8s-clusterip
                port:
                  number: 3000
          - path: /
            pathType: Prefix
            backend:
              service:
                name: service-nginx-clusterip
                port:
                  number: 4000
kubectl apply -f ingress.yaml
# ingress.networking.k8s.io/hello-ingress created

kubectl get ingress
# NAME            CLASS   HOSTS   ADDRESS   PORTS   AGE
# hello-ingress   nginx   *                 80      25s

docker exec -it minikube /bin/bash

curl 127.0.0.1/hello
# [v3] Hello, Kubernetes!, From host: hellok8s-deployment-6d468456c7-xsr6t

curl 127.0.0.1/     
# <!DOCTYPE html>
# <html>
# <head>
# <title>Welcome to nginx!</title>
# ...

Namespace

在开发过程中,可能会有各种各样的环境,如开发环境、测试环境、生产环境等,这时候就需要使用到Namespace,它可以将不同的资源进行隔离,如Pod、Service、Deployment等。Pod、Service的名称在不同的命名空间中可以重复。
在之前的实践中,我们采用的命名空间是default,现在我们创建一个namesapce.yaml文件,这个文件定义了devtest两个命名空间。

apiVersion: v1
kind: Namespace
metadata:
  name: dev
---
apiVersion: v1
kind: Namespace
metadata:
  name: test
kubectl apply -f namespace.yaml
# namespace/dev created
# namespace/test created

kubectl get namespaces
# NAME              STATUS   AGE
# default           Active   3d
# dev               Active   29s
# ingress-nginx     Active   101m
# kube-node-lease   Active   3d
# kube-public       Active   3d
# kube-system       Active   3d
# test              Active   29s

在命名空间中创建和获取资源,只需要增加参数-n,如:

kubectl apply -f deployment.yaml -n dev

kubectl get pods -n dev

Configmap

通常,在不通的命名空间中的应用,可能会有不同的配置,这时候就需要使用到Configmap,它可以将配置信息存储在k8s集群中。
注意Configmap在设计上不是用来保存大量数据的,所以Configmap只能保存1M的数据,如果要保存大量数据,应该挂载存储卷。
修改golang-demo代码,从系统环境读取DB_URL,代码如下:

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func hello(w http.ResponseWriter, r *http.Request) {
    host, _ := os.Hostname()
    dbURL := os.Getenv("DB_URL")
    io.WriteString(w, fmt.Sprintf("[v4] Hello, Kubernetes! From host: %s, Get Database Connect URL: %s", host, dbURL))
}

func main() {
    http.HandleFunc("/", hello)
    http.ListenAndServe(":3000", nil)
}

重新编译镜像,标签为hellok8s:v4

docker build . -t naisev/hellok8s:v4
docker push naisev/hellok8s:v4
kubectl delete deployment,service,ingress --all

接下来创建不同 namespace 的 configmap 来存放 DB_URL。

apiVersion: v1
kind: ConfigMap
metadata:
  name: hellok8s-config
data:
  DB_URL: "http://DB_ADDRESS_DEV"
apiVersion: v1
kind: ConfigMap
metadata:
  name: hellok8s-config
data:
  DB_URL: "http://DB_ADDRESS_TEST"

应用文件

kubectl apply -f hellok8s-config-dev.yaml -n dev
# configmap/hellok8s-config created

kubectl apply -f hellok8s-config-test.yaml -n test
# configmap/hellok8s-config created

kubectl get configmap --all-namespaces
# NAMESPACE         NAME                                 DATA   AGE
# dev               hellok8s-config                      1      12s
# test              hellok8s-config                      1      14s

接下来创建hellok8s.yaml文件,以Pod方式运行一个hellok8s容器

apiVersion: v1
kind: Pod
metadata:
  name: hellok8s-pod
spec:
  containers:
    - name: hellok8s-container
      image: naisev/hellok8s:v4
      env:
        - name: DB_URL
          valueFrom:
            configMapKeyRef:
              name: hellok8s-config
              key: DB_URL

分别在dev和test环境创建hellok8s,转发端口,通过curl访问查看效果。

kubectl apply -f hellok8s.yaml -n dev
# pod/hellok8s-pod created

kubectl apply -f hellok8s.yaml -n test
# pod/hellok8s-pod created

kubectl port-forward hellok8s-pod 3000:3000 -n dev

curl 127.0.0.1:3000
# [v4] Hello, Kubernetes! From host: hellok8s-pod, Get Database Connect URL: http://DB_ADDRESS_DEV

kubectl port-forward hellok8s-pod 3000:3000 -n test

curl 127.0.0.1:3000
# [v4] Hello, Kubernetes! From host: hellok8s-pod, Get Database Connect URL: http://DB_ADDRESS_TEST
Comments are closed.