本篇文章以Ubuntu-20.04系统为例,主要介绍如何使用K8s部署简单的Golang应用集群,参考了k8s-tutorials。
准备工作
安装Docker
下载Docker Engine,根据官方文档进行安装
Set up the repository
- 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
- 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
- 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
- Update the apt package index:
sudo apt-get update
- Install Docker Engine, containerd, and Docker Compose.
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- 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 命令速查
minikube stop
不会删除任何数据,只是停止 VM 和 k8s 集群。minikube delete
删除所有 minikube 启动后的数据。minikube ip
查看集群和 docker enginer 运行的 IP 地址。minikube pause
暂停当前的资源和 k8s 集群。minikube status
查看当前集群状态。
设置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 命令速查
kubectl logs -f hellok8s
查询 Pod 日志kubectl exec hellok8s -- ls
进入容器执行命令kubectl delete pod hellok8s
通过Pod name删除Podkubectl delete -f hellok8s.yaml
通过yaml删除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
文件,修改image
为naisev/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
: 逐渐增加新版本的 pod,逐渐减少旧版本的 pod。Recreate
: 在新版本的 pod 增加前,先将所有旧版本 pod 删除。
大多数情况下我们会采用滚动更新 (RollingUpdate) 的方式,滚动更新又可以通过 maxSurge 和 maxUnavailable 字段来控制升级 pod 的速率,具体可以详细看官网定义:
maxSurge
: 最大峰值,用来指定可以创建的超出期望 Pod 个数的 Pod 数量。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=3
到deployment.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
中设置path
和port
,periodSeconds
字段指定了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有很多配置字段,可以使用这些字段精确地控制就绪检测的行为:
initialDelaySeconds
:容器启动后要等待多少秒后才启动存活和就绪探测器, 默认是 0 秒,最小值是 0。periodSeconds
:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。timeoutSeconds
:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。successThreshold
:探测器在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。failureThreshold
:当探测失败时,Kubernetes 的重试次数。 对存活探测而言,放弃就意味着重新启动容器。 对就绪探测而言,放弃意味着 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。
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
- 为什么 pod 不就绪 (Ready) 的话,kubernetes 不会将流量重定向到该 pod,这是怎么做到的?
- 前面访问服务的方式是通过 port-forword 将 pod 的端口暴露到本地,不仅需要写对 pod 的名字,一旦 deployment 重新创建新的 pod,pod 名字和 IP 地址也会随之变化,如何保证稳定的访问地址呢?。
- 如果使用 deployment 部署了多个 Pod 副本,如何做负载均衡呢?
k8s 提供了一种名叫Service
的资源为pod
提供一个稳定的Endpoint
。Servie
位于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 的值包括如下:
ClusterIP
:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。 这也是默认的 ServiceType。NodePort
:通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>,你可以从集群的外部访问一个 NodePort 服务。LoadBalancer
:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。ExternalName
:通过返回 CNAME 和对应值,可以将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。 无需创建任何类型代理。
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。
- hellok8s.yaml
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
- nginx.yaml
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
文件,这个文件定义了dev
和test
两个命名空间。
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。
- hellok8s-config-dev.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: hellok8s-config
data:
DB_URL: "http://DB_ADDRESS_DEV"
- hellok8s-config-test.yaml
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
本文由 admin 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Aug 6, 2025 at 11:04 pm