.NET 微服务实践(4)-部署服务到Docker 与 Kubernetes

文章声明:本文系油管上的一个系列(.NET Microservices – Full Course)教程的学习记录的第三章,有兴趣看可以原视频。本文通过一步一步地展示、详细介绍了如何将基于.NET Core的Web API部署到Docker以及Kubernetes中,以及过程中各个操作的说明解释。除此之外,还介绍了Docker和Kubetnetes必要的概念。

Docker

这部分视频教程介绍比较简单,本文主要参考了微软培训文档的资料。

容器/Container

Microsoft Learning: What is Container?:
容器是一个松散孤立的环境,使我们能够构建并运行软件包。这些软件包(被称作“容器镜像”)包括代码和所有依赖项在任何计算环境快速而可靠地运行应用程序。

容器化/Containerization

Microsoft Learning: What is software containerization?:
软件容器化是一种操作系统虚拟化方法,用于在不使用虚拟机 (VM) 的情况下部署和运行容器。 容器可在物理硬件上、云和 VM 中运行,还可以在多个操作系统上运行。

容器化VS虚拟机

Microsoft Learning: Compare virtual machines to containers (Video):
类似于在单个物理主机上运行多个虚拟机,你可以在单个物理或虚拟主机上运行多个容器。
与虚拟机不同,你不用管理容器的操作系统。 虚拟机就像是可以连接和管理的操作系统的实例,但容器是轻型的,其设计目的是实现动态创建、横向扩展和停止。 虽然可在应用程序需求增加的情况下创建和部署虚拟机,但容器的作用旨在使你能够响应基于需求的更改。使用容器,可以在出现崩溃或硬件中断时快速重启。

总结就是:虚拟机虚拟化计算机硬件,而容器虚拟化操作系统。

Docker是什么

Microsoft Learning: What is Docker:
Docker 是一个用于开发、交付和运行容器的容器化平台。 Docker不使用虚拟机监控程序,如果要开发和测试应用程序,可以在台式机或笔记本电脑上运行 Docker。 桌面版 Docker 支持 Linux、Windows 和 macOS。 对于生产系统,Docker 适用于服务器环境,包括 Linux 的多种变体和 Microsoft Windows Server 2016 及更高版本。

Docker引擎

Docker引擎:包含配置为客户端-服务器实现的多个组件,其中,客户端和服务器在同一主机上同时运行。 客户端使用 REST API 与服务器通信,该 API 还让客户端能够与远程服务器实例通信。
Docker客户端:名为docker的命令行应用程序,它为我们提供了一个命令行接口 (CLI),用于与 Docker 服务器进行交互。docker命令使用 Docker REST API 将指令发送到本地或远程服务器,并作为用于管理容器的主要接口。
Docker服务器:一个名为dockerd的守护程序。dockerd守护程序通过 Docker REST API 响应来自客户端的请求,并且可以与其他守护程序进行交互。 此外,Docker服务器还负责跟踪容器的生命周期。

Docker镜像

容器镜像/Image:一种包含软件的可移植程序包。 它在运行时便成为了我们的容器。容器是镜像的内存中实例。容器镜像是不可变的。 生成镜像后,无法更改该镜像; 更改镜像的唯一方法是创建新映像。 此特性可保证在生产环境中使用的镜像与在不同 环境中使用的相同。
可堆叠的统一文件系统/UnionfsUnionfs用于创建 Docker镜像。Unionfs是一种文件系统,允许以看似合并内容的方式堆叠多个目录(称为分支)。但是,内容在物理上是保持分开的。Unionfs允许在生成文件系统时添加和删除分支。假设要为之前的 Web 应用生成一个镜像:将 Ubuntu 发行版作为基础镜像叠加到引导文件系统之上;接下来,将安装 Nginx 和 Web 应用;有效地将 Nginx 和 Web 应用叠加到原始 Ubuntu 镜像之上;在通过镜像运行容器后,将创建最终的可写层。但是,当容器被销毁时,此层将不复存在。

基础镜像/Base Image:使用 Docker scratch镜像的镜像。scratch镜像是一种空容器镜像,不会创建文件系统层。 此镜像假设要运行的应用程序可以直接使用主机操作系统内核。
父级镜像/Parent Image:用于创建镜像的容器镜像。例如,将使用已基于Ubuntu的镜像,而不是从 scratch 创建镜像再安装 Ubuntu。甚至可以使用已安装 Nginx 的镜像。父级镜像通常包含一个容器操作系统。父级镜像和基础镜像都可用于创建可重复使用的映像。但是,基础镜像能够更好地控制最终镜像的内容——镜像是不可变的,只能对镜像进行添加操作而不能进行删减操作。

Docker Host

主机操作系统/Host OS:Docker 引擎运行依赖作系统。在 Linux 上运行的 Docker 容器共享主机操作系统内核,只要二进制文件可以直接访问操作系统内核,便无需容器操作系统。但是,Windows 容器需要容器操作系统。容器依靠操作系统内核来管理服务,例如文件系统、网络管理、进程调度和内存管理。
容器操作系统/Container OS:是已打包映像包含的操作系统。 我们可以灵活地在容器中包含不同版本的 Linux 或 Windows 操作系统。 这种灵活性使我们能够访问特定操作系统功能或安装应用程序可能使用的其他软件。容器操作系统独立于主机操作系统,我们要在这一环境中部署和运行应用程序。 结合映像的不可变性,这种隔离意味着应用程序开发环境和生产环境相同。在示例中,使用 Ubuntu Linux 作为容器操作系统,并且此操作系统不会因开发或生产而改变。 我们所使用的映像始终是同一映像。

Docker Hub

Docker Hub是一个软件即服务 (SaaS) Docker 容器注册表。 Docker 注册表是用于存储和分发创建的容器镜像的存储库。 Docker Hub是Docker用于镜像管理的默认公共注册表。可以创建和使用专用 Docker 注册表,也可以使用不同公司提供的注册表服务。

Dockerfile

一种文本文件,其中包含用于生成和运行 Docker镜像的说明,可以定义镜像的以下方面:

  • 用于创建新镜像的基础映像或父级镜像
  • 用于更新基础操作系统和安装其他软件的命令
  • 要包含的生成项目,例如开发的应用程序
  • 要公开的服务,例如存储和网络配置
  • 要在启动容器时运行的命令

平台服务容器化

可以参考Docker的文档

配置Dockfile文件

在平台服务的工程文件目录下创建Dockerfile文件

  1. 声明父级镜像
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
  1. 配置(容器运行时)工作目录
WORKDIR /app
  1. 拷贝工程文件并将其作为独立的层
COPY *.csproj ./
RUN dotnet restore
  1. 拷贝其它项目文件并且编译
COPY . ./
RUN dotnet publish -c Release -o out

截至目前,所做都是拉取一个.NET Core的SDK、利用它做编译工作。之后将再获取一个运行时的父级镜像,打包出一个生产环境的镜像——相比于开发环境会更加小巧。

  1. 拷贝运行时父级镜像,设置工作路径,拷贝编译后的工程,并且指定程序运行的入口
FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "PlatformService.dll"]

制作平台服务的容器镜像

在Dockfile所做的目录下,运行如下Docker命令

docker build -t <docker hub id>/platformservice .

运行完成后,可以通过Docker Desktop查看;也可以通过命令查看;还可以在VS Code中安装Docker插件查看。


运行平台服务镜像

直接运行命令

docker run -p 8080:80 -d <docker hub id>/platformservice

这个命令中,8080是本地的端口、映射到容器内部的80端口。运行完成后,可以通过Docker Desktop查看;也可以通过命令查看;还可以在VS Code中安装Docker插件查看:


测试镜像

这里只能采用POST Man进行测试

这里的访问端口进行本地为container分配的8080端口。

推送容器镜像到Docker Hub

执行以下命令:

docker push <docker hub id>/platformservice

之前制作的镜像会分层推送到Hub上

Kubernetes

这部分视频教程介绍比较简单,本文主要参考了微软培训文档的资料,主要解释必要的K8s概念。

容器管理/Container Management

容器管理是组织、添加、删除或更新大量容器的过程。
例如某应用可涉及多项微服务,负责缓存、排队或数据处理等任务。其中每一项任务均托管在一个容器中,可以单独部署、更新和缩放。假设在特定时间需要更多站点缓存服务实例才能使性能不下降,于是需要添加更多的缓存服务容器实例;又或者你更新了缓存服务,那么需要批量更新所有的可用容器。
容器管理就是帮助处理这些本需要手动处理的业务。

容器编排/Container Orchestration

容器编排是自动部署和管理容器化应用的系统。例如,业务流程协调程序可以动态响应环境中的变化,以增加或减少部署的托管应用实例。 或者,如果发布了新版本的服务,则它可以确保所有已部署的容器实例得到更新。

计算机集群/Computer Cluster

群集是一组计算机,可配置为协同工作并视为单个系统;其中配置的计算机通常会执行相同类型的任务(例如,它们会托管网站、API 或运行计算密集型工作)。这些任务通常是群集使用中心化软件来进行调度和控制的,运行任务的计算机称为“节点/Node”,运行中心化软件的计算机称为“控制平面/control planes”。

Kubernetes/K8S

总结就是:Kubernetes可以作容器编排和群集管理软件来部署应用。 它的特点包括

  • 不考虑复杂的容器管理任务,主要涉及
  • 自行修复/Self-healing
  • 动态拓展/Dynamic scaling
  • 滚动更新/Rolling updates
  • 提供声明性配置,以便在不同的计算环境中协调容器。

Kubernetes Architecture

Kubernetes 群集至少包含一个主平面以及一个或多个节点。控制平面和节点实例都可以是物理设备、虚拟机或云中的实例。Kubernetes中的默认主机 OS 是 Linux,默认情况下支持基于 Linux 的工作负载。

Kubernetes Control Plane

运行一个服务集合,该集合来实现对节点的编排管理功能,包括群集组件通信、工作负载计划以及群集状态持久化等等。

Control Plane由以下内容构成

  • API 服务器/API server
  • 后备存储/Backing store
  • 计划程序/Scheduler
  • 控制器/Controller

API server:可被视为 Kubernetes 群集中 Control Plane 的前端。 Kubernetes 中的组件之间的所有通信都是通过此 API 完成的。此 API 公开一个 RESTful API,通过该 API 可以发布指令或基于 YAML 的配置文件。 YAML 是编程语言的可读数据序列化标准。使用 YAML 文件可定义 Kubernetes 群集中所有对象的预期状态。
Backing store:一个持久性存储,Kubernetes 群集使用它来保存 Kubernetes 群集的完整配置。 Kubernetes 使用名为 etcd 的可靠的高可用性分布式键值存储。 此键值存储存储当前状态以及群集中所有对象的所需状态。在生产 Kubernetes 群集中,Kubernetes 的官方指导意见是安排三到五个 etcd 数据库的复制实例,以实现高可用性。
Scheduler:负责在所有节点间分配工作负载的组件。 一般是监视群集中是否有新创建的容器,并将它们分配给节点。
Controller:Kubernetes 利用控制器跟踪群集中对象的状态。 在监视和响应群集事件的过程中,每个控制器都在非终止循环中运行。 例如,有一些控制器用于监视节点、容器和终结点。控制器与 API 服务器通信,以确定对象的状态。 如果对象的当前状态与所需状态不一致,则控制器将采取措施来确保所需状态。

Kubernetes Node

计算工作负载运行的位置。每个节点都通过 API 服务器与Control Plan通信,以通知它节点上的状态改变。

以下服务在 Kubernetes 节点上运行:

  • kubelet
  • kube-proxy
  • 容器运行时/Container Runtime

kubelet:kubelet 是一个代理,负责监视 API 服务器的工作请求,确保请求的工作单元正在运行并一切正常;同时负责监视当前容器按预期运行。
kube-proxy:组件负责本地群集网络,确保每个节点都具有唯一的 IP 地址;还实行使用 iptables 和 IPVS 处理流量的路由和负载平衡的规则。
Container Runtime:运行容器的基础软件。运行时负责提取、启动和停止容器映像。 Kubernetes 支持多种容器运行时,包括但不限于 Docker、rkt、CRI-O、containerd 和 frakti。 对多种容器运行时类型的支持都是基于容器运行时接口 (CRI) 实现的。 CRI 是一种插件设计,允许 kubelet 与可用容器运行时通信。

Kubernetes Pod

Pod 表示在 Kubernetes 中运行的应用的单个实例,且是 Kubernetes 平台上的原子单元。在 Kubernetes 上运行的工作负载是容器化应用,与 Docker 环境不同,不能直接在 Kubernetes 上运行容器,因此容器需要被打包到称为 Pod 的 Kubernetes 对象中。

单个 Pod 可容纳一组容器(可以是一个或多个容器)。 但是 Pod 通常不包含同一个应用的多个实例。Pod 包含有关共享存储和网络配置的信息,以及有关如何运行其打包容器的规范。
每个 Pod 都与调度它的工作节点绑定,并保持在那里直到终止(根据重启策略)或删除。 如果工作节点发生故障,则会在群集中的其他可用工作节点上调度相同的 Pod。

Kubernetes 网络

假设Kubernetes 群集有一个控制平面和两个节点。 将节点添加到 Kubernetes 时,IP 地址会被自动分配到内部专用网络范围内的每个节点。 例如,假设本地网络范围为 192.168.1.0/24,则这两个节点的IP地址可能为

部署的每个 Pod 将分配获得 IP 地址池中的一个 IP。 默认情况下,使用不同 IP 地址范围的 Pod 和节点不能互相通信。

Kubernetes Service

Kubernetes Service是为 Pod 提供稳定网络的 Kubernetes 对象。 使用 Kubernetes 服务可以实现节点间、Pod 间以及群集内外应用用户间的相互通信。

Kubernetes Storage

Kubernetes 采用与 Docker 一样的存储卷概念。 Docker 卷所受的管理比 Kubernetes 卷少,因为 Docker 卷生存期不受管理。 Kubernetes 卷的生存期是与 Pod 生存期匹配的显式生存期。 这种生存期上的匹配意味着卷的生存期比 Pod 中运行的容器要长。 不过当 Pod 被删除时,卷也会被删除。Kubernetes 提供了使用 PersistentVolumes 预配持久存储的选项。

Kubernetes Object

在 Kubernetes 系统中,Kubernetes Object 是持久化的实体。 Kubernetes 使用这些实体去表示整个集群的状态。特别地,它们描述了如下信息:

  • 哪些容器化应用在运行(以及在哪些节点上)
  • 可以被应用使用的资源
  • 关于应用运行时表现的策略,比如重启策略、升级策略,以及容错策略

通过使用Kubernetes API 操作 Kubernetes 对象,一般有以下2种方式:

  • kubectl命令工具
  • Client library的方式,例如client-go

通过设置对象 spec(规约)和 对象 status(状态),管理对象的配置。

  • spec:该内容由用户提供,描述用户期望的对象特征及集群状态。
  • status:该内容由kubernetes集群提供和更新,描述kubernetes对象的实时状态。

任何时候,Kubernetes Control Plane都会控制集群的实时状态 status 与用户的预期状态 spec 一致。

Kubernetes Architecture For Microservice

基于以上的Kubernetes概念/术语介绍,这里给出本次微服务搭建中Kubernetes的架构。

Configure Kubernetes .yaml file

当使用 Kubernetes API 创建对象时(或者直接创建,或者基于kubectl), API 请求必须在请求体中包含 JSON 格式的信息。大多数情况下,需要在 .yaml 文件中为kubectl提供这些信息。kubectl在发起 API 请求时,将这些信息转换成 JSON 格式。

  • apiVersion- 创建该对象所使用的 Kubernetes API 的版本
  • kind - 想要创建的对象的类别
  • metadata - 帮助唯一性标识对象的一些数据,包括一个 name 字符串、UID 和可选的 namespace
  • spec - 你所期望的该对象的状态
apiVersion: apps/v1
kind: Deployment
metadata:
name: platforms-depl
spec:
replicas: 1
selector:
matchLabels:
app: platformservice
template:
metadata:
labels:
app: platformservice
spec:
containers:
- name: platformservice
image: <Docker Hub Id>/platformservice:latest

一些解释:

  • 顶层metadata中的name描述了本次的部署
  • 由于不需要横向/水平拓展,因此replicas项设置为1
  • selector中的apptemplatemetadataapp一致,是描述了部署时找到platformservice为名的template

  • 减号符表示之后有连续、同级的配置(可以理解为数组或者集合)

基于.yaml文件部署服务到Kubernetes

进入.yaml文件所在的目录

检查Kubernetes是否安装(首先要在Docker Desktop中Enable Kubernetes)

>>kubectl version
Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.4", GitCommit:"3cce4a82b44f032d0cd1a1790e6d2f5a55d20aae", GitTreeState:"clean", BuildDate:"2021-08-11T18:16:05Z", GoVersion:"go1.16.7", Compiler:"gc", Platform:"windows/amd64"}
Server Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.4", GitCommit:"3cce4a82b44f032d0cd1a1790e6d2f5a55d20aae", GitTreeState:"clean", BuildDate:"2021-08-11T18:10:22Z", GoVersion:"go1.16.7", Compiler:"gc", Platform:"linux/amd64"}

运行部署命令

>>kubectl apply -f platforms-depl.yaml
deployment.apps/platforms-depl created

可以检查下部署

>>kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
platforms-depl 0/1 1 0 6s

还可以检查下Pod

>>kubectl get pods
NAME READY STATUS RESTARTS AGE
platforms-depl-78dc566c9-n79kk 1/1 Running 0 4m49s

最后可以在Docket Desktop查看建立的Kubernetes。

此时可以有个程序运行。实际上一个是Pod,另一个是Container。

此外,还做了一些尝试,描述的是模拟系统崩溃的时候(Container被删掉)【[操作1】,Pod是可以自行恢复的【操作2】(根据描述文件,Kubernetes Control Plane会将当前状态改成期望状态——而期望状态中replicas要求必须有一个容器实例)。只有删掉部署配置文件【操作3】,Pods才会被删除【操作4】

配置Kubernetes连接服务

前面部署Docker的时候,提到过将本地的8080端口映射成80端口

docker run -p 8080:80 -d <docker hub id>/platformservice

对于Kubernetes部署,已经设置了内部的端口为80端口(可以在Docker Desktop中点击容器进入查看)

因此需要部署服务来保证外部能够Pin到K8S内的Container。

apiVersion: v1
kind: Service
metadata:
name: platforms-svc
spec:
type: NodePort
selector:
app: platformservice
ports:
- name: platformservice
protocol: TCP
port: 80
targetPort: 80
nodePort: 30001

Service: An abstract way to expose an application running on a set of Pods as a network service.

首先理解服务。假设我们开发并且部署的容器在一组Pods里(多个横向拓展),我们将其作为后端;然后将集群内部其它Pods组(同一类开发并且部署的容器)或者集群外部的服务作为前端。那么K8S中的“服务”则是一种抽象、一种三方中转,(两类)前端通过它访问后端、而不需要知道哪个特定的后端Pod,后端也通过它访问前端。对于目标容器所在的某组Pod而言

  • Node Port是服务暴露给Kubernetes外部服务的端口(手动设置的有效区间是30000-32767,如果不设定、会自动分配),外部服务通过这个端口与该服务通信、最终访问到后端
  • Port是服务暴露给Kubernetes集群内其它Pods组的端口,集群内的其他Pods可以在指定端口与该服务通信、最终访问到后端
  • Target Port则暴露了容器所在的Pods组(需要Pod本身开放了该端口)港口的服务将发送请求,那你将监听仓。

此外,服务配置的Yaml文件中Selector中的app、port中的name以及之前部署的服务的appname需要一致,是描述了部署服务时找到对应的服务。

应用Yaml文件

>>kubectl apply -f platforms-svc.yaml
service/platforms-svc created

检查Service状态

>>kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d12h
platforms-svc NodePort 10.98.208.36 <none> 80:30001/TCP 11s

然后就可以使用Postman进行测试了

posted @   李嘉图正在调试  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示