k8s集群部署.Net8
前言
- 网上关于k8s部署.net的资料比较少,年代久远,且部署方式也只适用于教学,不适用实际生产环境,基本上没有多台服务器在k8s集群下部署.net8的资料,很多都是伪集群,要么1台机器上部署一个实例,要么1台机器上部署多个实例。
- 自己也是k8s刚入门的新人,部署的过程中遇到了非常多的问题,花费了无数个白天黑夜解决问题,期间通过百度或者AI解决,文章中可能并没有展示所遇到的全部问题,因为基本上都是提前解决了坑,如果不按照我的方式配置就可能出现各种问题。
- docker镜像部署到k8s集群中,k8s默认使用的是远程仓库中的镜像,因此需要我们将本地镜像推送到远程仓库,然后再从远程仓库拉取,这个过程较为复杂不是很熟悉,所以这里不将本地构建的镜像推送到远程仓库中,而是直接在k8s中使用本地的docker镜像。
- 在看这篇文章前,前先阅读之前的文章,即在centos下实现k8s集群,若未实现k8s集群则部署过程中会出现问题。
- 文章中为了方便部署防火墙是关闭状态的,当然你也选择不关闭,但必须要开放指定的端口,否则会出现无法访问的情况。
演示工具
VmWare17、MobaXterm
系统版本
CentOS7
集群节点
角色 | IP地址 |
---|---|
k8s-master | 192.168.12.136 |
k8s-slave1 | 192.168.12.137 |
k8s-slave2 | 192.168.12.138 |
创建.Net8项目并修改配置
- 打开Program.cs文件,在app.MapControllers()前面一行加上下列代码实现重定向,方便在浏览器中通过ip+端口的方式直接访问服务。
app.MapGet("/", (HttpResponse response) =>
{response.Redirect("/WeatherForecast");return Task.CompletedTask;
});
- 修改DockerFile文件,修改默认配置,改成如下内容。
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080
COPY . .
ENV TimeZone=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone
ENTRYPOINT ["dotnet", "CoreDocker.Service.Api.dll"]
- 修改控制器WeatherForecastController,将原来的方法Get注释,换成以下内容。
该接口返回Pod的主机名和IP信息。
[HttpGet(Name = "GetWeatherForecast")]
public IActionResult Get()
{string hostName = Dns.GetHostName();string hostIP = Dns.GetHostEntry(hostName).AddressList.FirstOrDefault(x => x.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).ToString();return new JsonResult(new { hostName, hostIP });
}
发布应用程序
本地运行项目,若正常运行,则发布程序。
创建目录存放应用程序
在master节点上创建目录,将应用程序上传到master节点新创建目录中。
mkdir -p /opt/netproject/demo/k8sdemo
构建镜像
进入k8sdemo目录中,然后构建镜像。
cd /opt/netproject/demo/k8sdemo
构建镜像。
#命令最后的.号别漏了,因为DockerFile文件是和应用程序处于同一级目录,加上.表示会在当前目录下查找 Dockerfile 文件以及构建过程中需要的其他文件。docker build -t k8sdemo:1.0 .
注意:如果需要将本地镜像推送到私有镜像仓库,还需要执行以下命令(网上摘取,未尝试过)
# 标记镜像
docker tag k8sdemo:1.0 <your-registry>/k8sdemo:1.0# 推送镜像到私有镜像仓库
docker push <your-registry>/k8sdemo:1.0
查看镜像
docker images
保存本地镜像为tar文件
这一步和后面几步的目的是为了将镜像文件分配到每个集群节点上,如果只在master节点构建镜像,通过yaml文件创建Pod时会出现问题,状态会显示为 ErrImagePull 或 ImagePullBackOff。
docker save -o k8sdemoimage.tar k8sdemo:1.0
将tar文件上传到k8s slave节点上
在slave节点上创建目录用来存放主节点传过来的tar文件。
mkdir -p /opt/netproject/demo/k8sdemo
使用scp命令将master节点上的tar文件推送到slave节点上。
scp k8sdemoimage.tar root@192.168.12.137:/opt/netproject/demo/k8sdemo
scp k8sdemoimage.tar root@192.168.12.138:/opt/netproject/demo/k8sdemo
在slave节点上查看tar文件是否已推送过来
cd /opt/netproject/demo/k8sdemo
ls
在k8s slave节点上加载镜像
- 从tar格式的归档文件中加载镜像,并将这些镜像导入到本地的 Docker 镜像库中。
docker load -i /opt/netproject/demo/k8sdemo/k8sdemoimage.tar
- 查看镜像列表
docker images
创建容器
在master节点上基于本地镜像创建容器。
docker run --name k8sdemocontainer -p 8089:8080 -d k8sdemo:1.0
查看运行中的容器,确定其正在运行。
docker ps
注意事项:执行docker run命令的时候可能会报以下错误,虽然容器创建成功了,但是启动出现异常。
解决步骤如下
- 检查防火墙是否关闭,若没关闭则关闭。
systemctl status firewalld
- 尝试重启docker(有些时候不一定重启就可以解决,此时需要通过其它方式解决)
systemctl restart docker
- 重新启动容器
docker start k8sdemocontainer
测试容器是否运行正常
本地宿主机访问主节点ip+映射端口,看是否调用接口输出信息。
yaml部署k8s service
定义yaml文件,常见有两种做法,一是将kind定义为Pod,二是将kind定义为Deployment和Service。二者任选其一即可。
基于Pod
创建k8sdemopod.yaml文件
编辑内容如下
apiVersion: v1
kind: Pod
metadata:name: k8sdemolabels:app: k8sdemo
spec:containers:- name: k8sdemocontainerimage: k8sdemo:1.0imagePullPolicy: Neverports:- containerPort: 8080livenessProbe:httpGet:path: /port: 8080
参数解析:
# 指定Api的版本
apiVersion: v1
# 指定k8s资源的类型,常见的有Deployment、Service、Pod等
kind: Pod
# 包含资源的元数据,如name、labels等。
metadata:# Pod名称name: k8sdemo# Pod自定义标签,查看的时候可以使用--show-labels参数来显示Pod对应的标签,例如kubectl get pods --show-labelslabels:app: k8sdemo
# 包含此资源的规格信息,例如容器的镜像、端口、卷挂载等
spec:
# 容器信息,包含容器的镜像、端口、环境变量等,可以定义一个或多个容器containers:# 容器的名称- name: k8sdemocontainer# 容器使用的镜像名称:标签image: k8sdemo:1.0# 镜像拉取策略,有Always(默认值,每次启动Pod都会尝试从远程仓库拉取最新版本镜像)、IfNotPresent(本地存在用本地的,不存在远程拉取)、Never(不从远程仓库拉取,只用本地的)三个值imagePullPolicy: Never# 端口信息ports:# 容器监听端口- containerPort: 8080# 健康检查,如果探测失败就会重启容器livenessProbe:# 通过http get的方式访问容器IP地址,并指定端口和路径,如果响应码会2xx或3xx视为成功httpGet:# 访问路径path: /# 访问端口port: 8080
上传yaml文件
在master节点上执行以下命令创建目录。
mkdir -p /opt/myyaml/
将本地yaml文件上传到此目录中。
创建pod资源
#进入该目录
cd /opt/myyaml#根据yaml文件创建pod资源
kubectl create -f k8sdemopod.yaml
查看pod状态
若STATUS列显示为Running且READY列显示为1/1,则表示成功。
kubectl get pods
#实时监控运行状态并显示在终端上
kubectl get pods --watch
注意:
- 状态值有以下几种,分别为 ContainerCreating、ErrImagePull、ImagePullBackOff、Running、Pending、Succeeded、Failed、Unknown,具体如下(我遇到过的状态目前只有前面5种,后面3种暂时未遇到过)
- ContainerCreating:表示集群中的Pod正在创建容器。
- ErrImagePull:因为镜像不存在、仓库不可访问、权限问题等原因导致拉取镜像失败,未进入退避状态,失败后 Kubernetes 会立即重试。
- ImagePullBackOff:由于多次拉取镜像失败导致的状态,通常和
ErrImagePull
状态持续发生有关,拉取镜像失败且多次重试后进入退避状态,表示 Kubernetes 会暂停一段时间再重试,避免过于频繁的重试。 - Pending:挂起状态,Pod 已被 Kubernetes 系统接收,但仍有一个或多个容器未被创建。这可能是由于调度器正在寻找合适的节点来运行容器,或者正在等待容器镜像下载或其他初始化操作。
- Running:运行中状态,Pod 已经被绑定到一个节点上,并且所有的容器都已经被创建,而且至少有一个是运行状态,或者是正在启动或者重启。Pod状态为Running,并不代表Pod正常,也需要看READY。
- Succeeded:所有容器执行成功并终止,并且不会再次重启。
- Failed:所有容器都已终止,并且至少有一个容器以失败的方式终止,也就是说这个容器 要么以非零状态退出,要么被系统终止。
- Unknown:通常是由于通信问题造成的无法获得 Pod 的状态。
- 若状态显示为 ErrImagePull 或 ImagePullBackOff 状态,此时需要通过kubectl describe pod命令查看pod具体报错信息。
若状态显示为ContainerCreating,可以耐心等待一下再执行命令查看,或者可以在命令后面加上--watch参数,这样Pod的运行状态会实时显示在终端上。
kubectl describe pod <podname> -n kube-system
本地端口绑定到k8s集群中pod端口
kubectl port-forward 是 Kubernetes 命令行工具 kubectl 提供的一个功能,用于在本地主机和 Kubernetes 集群中的 Pod 之间建立端口转发。
当你运行 kubectl port-forward 命令时,它会将本地主机上的一个端口与 Kubernetes 集群中的一个 Pod 的端口进行绑定。这样,在本地主机上监听的端口上收到的流量将被转发到Pod的端口上。
该命令的优点如下
- 访问远程 Pod 的本地服务:你可以将 Pod 的端口转发到本地主机,从而能够直接访问 Pod 上运行的服务。例如,你可以将一个运行在 Kubernetes 集群中的数据库 Pod 的端口转发到本地,以便在本地开发环境中连接和测试数据库。
- 调试和日志记录:通过将 Pod 的端口转发到本地,你可以使用本地工具来调试和监视在 Kubernetes 中运行的应用程序。你可以使用本地的调试器、日志记录工具或其他开发工具来检查应用程序的状态、调试问题或查看日志。
- 绕过 Kubernetes 服务和负载均衡器:有时候,你可能想直接访问运行在 Kubernetes 中的应用程序,而不经过 Kubernetes 的服务发现和负载均衡机制。通过将 Pod 的端口转发到本地,你可以绕过这些机制,直接连接到应用程序。
语法
kubectl port-forward <pod-name> [local-port]:pod-port [-n namespace]
在master节点输入如下命令
kubectl port-forward --address 0.0.0.0 k8sdemo 8090:8080
参数解析:
--address:指定要监听的地址。若不填此参数,则默认只在本地机器上监听转发,即127.0.0.1,如果需要从外部访问或集群内其它节点访问该端口转发,需要将值设置为0.0.0.0,这样可以监听所有的网络接口。
k8sdemo:k8s集群中Pod的名称。
8090:8080:端口映射配置,
- 8090:这是本地机器上用于监听的端口。当你在本地机器上访问 localhost:8090 或者使用监听地址对应的 IP 加上 8090 端口时,kubectl会把这个请求转发到 Kubernetes 集群中的 Pod 上。
- 8080:这是目标 Pod 中要转发到的端口。也就是说,kubectl 会将本地 8090 端口接收到的请求转发到名为 k8sdemo 的 Pod 的 8080 端口上。
注意事项:kubectl port-forward是一个阻塞式命令,在命令执行期间,终端会一直处于阻塞状态,直到你手动停止为止(例如Ctrl+C)。
slave节点输入以下命令访问
#注意这里必须加上/WeatherForecast,因为curl默认情况下不会自动处理重定向
curl 192.168.12.136:8090/WeatherForecast
本地宿主机在浏览器中输入以下地址访问
#加不加后面的/WeatherForecast都行,因为浏览器会自动处理重定向状态码
http://192.168.12.136:8090/WeatherForecast
注意:若防火墙未处于关闭状态,最好开启特定端口
sudo firewall-cmd --permanent --add-port=8091/tcp
sudo firewall-cmd --reload
将Pod资源暴露成k8s service
上一步中使用 kubectl port-forward 命令通过端口转发的方式实现访问, 但该方式常用于开发环境下单节点临时访问,生产环境中通用使用expose创建service。kubectl port-forward --address 0.0.0.0命令执行后,不管是集群节点也好,还是外部宿主机也好,都只能通过执行命令节点的IP访问,无法通过任意节点IP访问,而expose service会自动在所有集群节点上开放固定端口,外部宿主机可以使用集群内的任意节点访问。
下面将演示如何将指定Pod资源暴露为k8s service。
在master节点输入如下命令:
kubectl expose pod k8sdemo --name k8sdemo-service --type=NodePort --port=8091 --target-port=8080
参数解析:
- --name(k8sdemo-service):指定创建service的名称。
- k8sdemo:要暴露的Pod的名称。
- --type(NodePort):指定service的类型,例如ClusterIP(默认)、NodePort、LoadBalancer、ExternalName。这里指定NodePort,该类型的服务会在每个节点上开放一个端口,外部客户端可以通过节点的 IP 地址和该端口访问服务。
- --port(8091):指定服务对外暴露的端口号,通过此端口号可以从集群外部访问服务。
- --target-port(8080):指定服务将流量转发到 Pod 的端口号,客户端访问服务的 8091 端口时,流量会被转发到 k8sdemo Pod 的 8080 端口。通常与容器端口相同。若不指定此参数,则默认值和port值一样。
获取k8s service列表
kubectl get services
可以看到k8s服务已经显示成功。
- NAME(k8sdemo-service):服务的名称。
- TYPE(NodePort):k8s服务的一种类型,用于将集群内部的服务暴露给外部客户端,它会在每个集群节点上开放一个特点的端口(范围默认是30000-32767),外部客户端可以通过任何一个节点的IP地址加上这个开放的端口来访问集群内部的服务。
- PORT(8091:31728/TCP):8091是服务端口,集群内部的Pod通过该服务的CLUSTER-IP和该端口号进行通信,或者在集群节点上通过命令行工具(例如curl)访问服务时使用此端口访问,它用于在集群内部实现服务发现和负载均衡;31728是节点端口,K8S会在每个集群节点上开放一个特点的端口(范围默认是30000-32767),外部客户端(例如宿主机)可以通过集群中任何一个节点的IP地址加上这个开放的端口来访问集群内部的服务。
- CLUSTER-IP(10.1.97.2):服务在集群内部的虚拟IP地址。K8S通过此字段实现服务发现、负载均衡和内部通信。
集群节点访问service
集群节点访问k8s service是通过CLUSTER-IP+Server Port端口实现的。从前面的信息中得知CLUSTER-IP是10.1.97.2,Server Port是8091。
#注意这里必须加上/WeatherForecast,因为curl默认情况下不会自动处理重定向
curl 10.1.97.2:8091/WeatherForecast
192.168.12.136
192.168.12.137
192.168.12.138
宿主机访问service
宿主机访问k8s service是通过集群节点IP+Node Port端口实现的。从前面的信息中得知Node Port是31728。
#加不加后面的/WeatherForecast都行,因为浏览器会自动处理重定向状态码
http://192.168.12.136:31728/WeatherForecast
http://192.168.12.137:31728/WeatherForecast
http://192.168.12.138:31728/WeatherForecast
192.168.12.136
192.168.12.137
192.168.12.138
基于Deployment和Service
创建k8sdemodeploy.yaml文件
编辑内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:name: k8sdeploydemolabels:app: k8snet8demo-app
spec:replicas: 3selector:matchLabels:app: k8snet8demo-apptemplate:metadata:labels:app: k8snet8demo-appspec:containers:- name: k8sdemocontainerimage: k8sdemo:1.0 # 如果使用私有镜像仓库,修改为 <your-registry>/k8sdemo:1.0ports:- containerPort: 8080
参数解析
# 指定Api的版本,注意这里是固定值apps/v1,不能写v1,否则部署的时候会报错
apiVersion: apps/v1
# 指定k8s资源的类型
kind: Deployment
# 包含资源的元数据,如name、labels等。
metadata:# Pod名称name: k8sdeploydemo# Pod自定义标签,查看的时候可以使用--show-labels参数来显示Pod对应的标签,例如kubectl get pods --show-labelslabels:app: k8snet8demo-app
# 包含此资源的规格信息,例如容器的镜像、端口、卷挂载等
spec:# 指定想要运行的Pod副本数量replicas: 3# 查找和管理它所负责的Podselector:#Deployment 会查找所有带有app标签且其值为 k8snet8demo-app的PodmatchLabels:app: k8snet8demo-app# 定义用于创建Pod的模板,当Deployment需要创建新的Pod副本时,会根据这个模板来生成每个Pod的配置template:# 定义每个由该模板创建的Pod的元数据。元数据通常包含标签、注解等信息metadata:# 一组键值对,用于给Pod添加标签labels:app: k8snet8demo-app
# 容器信息,包含容器的镜像、端口、环境变量等,可以定义一个或多个容器containers:# 容器的名称- name: k8sdemocontainer# 容器使用的镜像名称:标签image: k8sdemo:1.0# 端口信息ports:# 容器监听端口- containerPort: 8080
注意:
- spec.selector.matchLabels.app 与 spec.template.metadata.labels.app 标签的值必须保持一致,只有当这两个地方的 app 标签值一致时,Deployment 才能准确识别出由它自己创建的 Pod 并对其进行管理,比如进行扩缩容、滚动更新等操作。
- spec.selector.matchLabels 是 Deployment 用于筛选和管理 Pod 的标签选择器。它定义了 Deployment 要管理哪些 Pod,即哪些 Pod 是属于这个 Deployment 的。
- spec.template.metadata.labels 是定义在 Pod 模板中的标签,当 Deployment 根据这个模板创建新的 Pod 时,这些标签会被添加到新创建的 Pod 上。
- metadata.labels.app的值可以和spec.selector.matchLabels.app 或者 spec.template.metadata.labels.app不一样。
创建k8sdemoservice.yaml文件
编辑内容如下:
apiVersion: v1
kind: Service
metadata:name: k8sservicedemo
spec:selector:app: k8snet8demo-appports:- protocol: TCPport: 8080targetPort: 8080nodePort: 31728type: NodePort
参数解析:
apiVersion: v1
kind: Service
metadata:name: k8sservicedemo
spec:selector:app: k8snet8demo-app #注意这里的取值要和deployment.yaml中的 selector 匹配ports:- protocol: TCP# Service端口port: 8080# 容器端口targetPort: 8080# 节点暴露端口(范围30000-32767)nodePort: 31728 # 可以选择 30000 - 32767 之间的端口type: NodePort
上传yaml文件
在master节点上执行以下命令创建目录。
mkdir -p /opt/myyaml/
将创建的两个yaml文件上传到master节点上,
开启防火墙(可选)
如果防火墙未处于关闭状态,则要允许nodePort配置的端口处于开放状态。
sudo firewall-cmd --permanent --add-port=8091/tcp
sudo firewall-cmd --reload
部署应用到集群
# 进入该目录
cd /opt/myyaml# 部署Deployment
kubectl apply -f k8sdemodeploy.yaml# 部署Service
kubectl apply -f k8sdemoservice.yaml
查看pod状态
kubectl get pods -o wide
确保所有Pod的状态(STATUS)都是Running。
master节点
slave1节点
slave2节点
注意:从图中可以看到会出现两个 Pod 被调度到 k8s - slave2 节点,而没有 Pod 调度到 k8s - master 节点的情况,这可能是正常现象,因为Kubernetes 调度器会根据一系列的调度策略和算法来决定将 Pod 调度到哪个节点上。这些策略包括但不限于节点的资源可用性、Pod 的资源请求、节点的亲和性和反亲和性设置等。至于为什么没有Pod调度到k8s - master 节点,可能是污点和容忍度问题,在很多 Kubernetes 集群部署中,master 节点会被设置污点(Taints),以防止普通 Pod 被调度到该节点上。污点可以理解为节点的一种 “排斥属性”,只有具有相应容忍度(Tolerations)的 Pod 才能被调度到带有这些污点的节点上。
可以通过以下命令查看k8s - master节点的污点,若执行命令后显示类似“Taints: node - role.kubernetes.io/master:NoSchedule”这样的提示,说明 master 节点设置了不允许普通 Pod 调度的污点。
kubectl describe node k8s-master | grep Taints
kubectl describe node k8s-slave1 | grep Taints
可以看到确实有这样的提示,如果改为slave节点不会有这样的问题。
查看deployment状态
kubectl get deployments
master节点
slave1节点
slave2节点
master节点看上去没什么问题,但是slave节点都报错了。从报错内容分析,大概率是权限问题,在 Kubernetes 中,每个操作都需要经过授权,而从节点的节点用户(system:node组下的用户)默认没有权限访问 deployments 这类资源。权限是通过 Kubernetes 的基于角色的访问控制(RBAC)机制来管理的。
解决方案
- 创建ClusterRole配置文件:node-deployment-access.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:name: node-deployment-access
rules:- apiGroups: ["apps"]resources: ["deployments"]verbs: ["get", "list", "watch"]
- 创建ClusterRoleBinding配置文件:node-deployment-access-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: node-deployment-access-binding
subjects:- kind: Groupname: system:nodesapiGroup: rbac.authorization.k8s.io
roleRef:kind: ClusterRolename: node-deployment-accessapiGroup: rbac.authorization.k8s.io
- 上传到/opt/myyaml目录中
- 进入该目录
cd /opt/myyaml
- 执行kubectl apply命令
kubectl apply -f node-deployment-access.yaml
kubectl apply -f node-deployment-access-binding.yaml
- 在slave节点上重新执行kubectl get deployments命令,可以看到问题已解决。
kubectl get deployments
查看service状态
集群服务器分别执行以下命令
kubectl get services
集群节点访问service
- 获取k8s service的信息
kubectl get svc k8sservicedemo
- 集群内部访问服务,这里有2种方式可以访问。
# 方式一:在任意集群节点上通过任意一个节点的IP地址+31728端口访问
curl 192.168.12.136:31728/WeatherForecast
curl 192.168.12.137:31728/WeatherForecast
curl 192.168.12.138:31728/WeatherForecast# 方式二:在任意集群节点上通过10.1.254.123+8080端口访问
curl 10.1.254.123:8080/WeatherForecast
获取Service的Cluster-IP
通过节点 IP + NodePort 访问(在集群内节点执行)
通过 ClusterIP 访问
宿主机访问service
#加不加后面的/WeatherForecast都行,因为浏览器会自动处理重定向状态码
http://192.168.12.136:31728/WeatherForecast
http://192.168.12.137:31728/WeatherForecast
http://192.168.12.138:31728/WeatherForecast