
我们需要构建的东西
- 一个使用minikube的Kubernetes环境,包含一个(1)主节点/控制平面节点和三个(3)工作节点。
- 一个Kubernetes应用部署,将部署一个三层应用架构(Web/Apache、Node.js和Postgres数据库)。
注意:在这个示例中,我们不会有任何应用程序的源代码。我们将更加关注应用程序的架构和基础设施。
先决条件
- 熟悉Linux系统和命令。
- 熟悉Docker容器和编排方法,如Docker Compose和Swarm。
- 您的系统已安装Docker。
- 访问命令行工具。
Kubernetes是什么?
与Docker Swarm类似,Kubernetes是领先的容器编排工具之一,但适用于更大型和更复杂的应用工作负载。它提供了更高的灵活性、可扩展性、可靠性和特定性,以便部署工作负载。
那么...Kubernetes比Docker Swarm更好,对吗?
不一定!尽管Docker Swarm更轻量级,但通常被认为更易于使用和扩展。就像技术领域的任何事物一样,这真的取决于想要构建的内容。
一些关键的区别
Kubernetes在Docker之上添加了一层抽象,因此它有自己的一套API来创建网络、容器和卷。就像 docker
一样,我们可以通过 kubectl
CLI访问API引擎。
Pods
Pods是部署的基本单位。它们与Swarm中的服务/容器非常相似,但在其上有一层抽象(你会经常听到这个词)。一个节点内可以有多个Pod,一个Pod内可以有多个容器。这可能有点令人困惑,但只要知道,当我们提到"Pods"时,就相当于"容器"。
服务:
在Swarm中,服务指的是容器/任务定义和部署。在Kubernetes中,服务指的是用于内部通信或与外部服务通信的Pod的网络和稳定的终端点/地址。有三种主要类型的服务:ClusterIP、NodePort和LoadBalancer。
让我们开始吧!
安装minikube和Hyperkit
在开始构建我们的容器化应用之前,我们需要创建一个本地开发环境。minikube是一个很棒的工具,它允许我们在本地机器上实现多节点的Kubernetes集群,并且已经预装了kubectl。
以你认为合适的方式在你的系统上安装minikube,但对于我来说(macOS),Homebrew软件包管理器是最直接的方法,它还允许我们安装所需的虚拟机环境。
yml
brew install minikube
要在本地运行一个多节点集群,minikube需要一个虚拟机驱动程序,因此我们需要安装一个虚拟机工具,比如Hyperkit、VirtualBox或Parallels。我将使用Hyperkit,但你可以自由选择。
yml
brew install hyperkit
集群设置
如果你还记得Docker Swarm,我们的容器工作负载运行在"节点"上,这些节点可以是物理或虚拟机/服务器,提供高可用性和容错性。在Swarm中,节点被分配为管理节点或工作节点的角色。在Kubernetes中,原则是相同的,但节点被分为控制平面的角色 - 包含所有主节点 - 和工作节点。让我们在minikube中设置我们的集群。
- 一个(1)主/控制平面节点。
- 三个工作节点。
默认情况下,minikube将我们的本地机器分配为主节点/控制平面节点,其他所有节点都作为工作节点。
yml
minikube start --driver hyperkit --nodes 4

现在,我们可以使用 kubectl
命令来获取我们的节点
yml
kubectl get nodes

限制Pod分配
我们只希望将Pod调度到工作节点上,因此我们需要向控制平面添加特殊指令,以形式化一个污点。污点允许节点通过告诉调度器不要调度任何Pod,除非它们具有匹配的容忍度来排斥一组Pod。
yml
kubectl taint nodes node_name key1=value1:NoSchedule

部署前端网页层
类似于Docker Compose,一个应用的整个基础设施可以使用单个YAML文件部署;然而,Kubernetes的YAML文件可能会更加复杂和详细。虽然可以使用单个YAML文件,但如果将部署配置拆分为几个有组织的文件,可能更容易管理。
我们将从前端Web层开始,部署包含Apache Web服务器的Pod。

我已将我的"kube_app"应用程序目录组织成"前端","后端"和"数据库"层。
前端网络服务
我们一直在使用Docker Swarm,所以你可能对"服务"这个术语很熟悉。在Docker中,服务是在容器上运行的镜像或镜像类型,比如Apache、NGINX、Node、Postgres等等。它本质上是一个容器/任务的描述和角色,将在集群中部署。
Kubernetes服务是指用于容器组内部通信或与外部服务通信的网络和稳定的终端/地址。主要有三种类型的服务:ClusterIP、NodePort和LoadBalancer。
ClusterIP创建虚拟IP以允许Pod之间进行通信。我们的集群被赋予了一个默认的ClusterIP,这样控制平面和工作节点就可以相互通信。
yml
kubectl get services

NodePort允许控制平面公开和分配特定端口以供外部访问。这是我们访问80端口上的网站所需的服务类型。
创建一个名为 frontend-kube-app.yml
的新YAML文件。
yml
apiVersion: v1
kind: Service
metadata:
name: web-httpd-service
spec:
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
selector:
app: web-httpd
---
每个Kubernetes对象或类型都指向特定的API组,因此了解应该指向哪个 apiVersion
非常重要。找到这个的一个简单方法是查看 api-resources
并找到对象的版本。
yml
kubectl api-resources

在 metadata
下,我们可以给我们的服务起一个名字(必填)。
我们将 type
指定为 Nodeport
,并开放80端口。
selector
的概念和标签非常重要。该服务使用选择器来允许流量访问具有匹配标签的Pod。
---
表示一个分隔符,表示对象的结束。
提示:不要试图在文档之间来回切换,你可以使用
explain
命令获取关于对象的每个部分的详细信息。例如,你可以使用点表示法来深入研究特定领域,比如kubectl explain service.spec
,它会给出该类别下每个选项的解释。
前端网站部署
现在我们准备创建一个部署!部署更类似于Docker服务。它是关于Pod的指令和规范的集合,包括镜像、副本数量、端口、重启策略等等...
在同一个文件中,在服务对象下面:
yml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-httpd-deployment
spec:
selector:
matchLabels:
app: web-httpd
replicas: 10
template:
metadata:
labels:
app: web-httpd
spec:
containers:
- name: web-httpd
image: httpd:2.4.55
ports:
- containerPort: 80
我们保持得很简单,但你可以看到更详细的规格会使这变得稍微复杂一些。
正如我们所看到的,Pod部署属于kind: Deployment,并拥有自己的API组。我们还应该注意到 selector
部分,我们给它一个与web-httpd-service选择器相同的 matchlabel
,这样它们就知道彼此如何连接。
在 template
下,我们可以提供容器的信息,例如名称、镜像和端口。
部署Pod和服务
我们准备部署我们的第一个Pod!这是容易的部分。我们所要做的就是配置并指向我们的YAML文件。
yml
kubectl apply -f ./frontend/frontend-kube-app.yml

我们可以运行 kubectl get all -o wide
来获取我们集群中所有服务和Pod的详细信息。

好的!我们的十个Pod都已经启动并运行,我们可以看到它们都没有分配到主节点/控制平面节点上。
我们还可以看到我们的NodePort服务及其端口映射。让我们尝试通过使用任何一个节点的IP地址和NodePort端口(右侧的数字)来访问Web服务器。我们可以通过运行 kubectl get nodes -o wide
来获取节点的IP地址。

它成功了!我们刚刚将我们的第一个Kubernetes pod部署到了我们的集群中!
部署后端应用层
既然我们已经完成了第一阶段,接下来基本上就是重复洗涤的过程了,只是根据您的需求来调整规格。
由于我们目前没有任何应用程序源代码或需要连接任何东西,我们只需为应用程序后端创建一个简单的 Deployment
。在 backend
目录中,创建一个名为 backend-kube-app.yml
的新YAML文件。
yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-app-deployment
spec:
selector:
matchLabels:
app: nodejs-app
replicas: 4
template:
metadata:
labels:
app: nodejs-app
spec:
containers:
- name: nodejs-app
image: node:19-alpine3.16
command: ["sleep", "100000"]
为了测试目的,我们将输入一段 command
的 sleep
,以保持容器的开启和运行。
再次运行 apply
命令并获取结果。
yml
kubectl apply -f ./backend/backend-kube-app.yml
yml
kubectl get all -o wide

太棒了!我们提供的4个Pod都成功运行了。
部署后端数据库层
我们已经准备好进入最后阶段,并为我们的应用程序部署数据库(Postgres)。数据库可能会稍微复杂一些,所以让我们来分解一下我们所需要的内容:
- 一个
ConfigMap
用于存储用户名和密码等秘密信息。 - 定义和分配存储空间给数据库A的
PersistantVolume
和PersistantVolumeClaim
。 - 连接数据库的
Service
- 定义并部署数据库Pod到我们的集群。
创建一个ConfigMap
将数据与代码分离始终是最佳实践。在开发中,通过ConfigMap是一个很好的方法来实现这一点。Kubernetes为此目的拥有自己的API组。
在数据库目录中,创建一个名为 postgres-config.yml
的新YAML文件
yml
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
labels:
app: postgres-db
data:
POSTGRES_DB: postgresdb
POSTGRES_USER: admin
POSTGRES_PASSWORD: mypass
在这里,我们将存储环境变量,例如数据库名称、用户和密码。再次特别注意 labels
,因为这是我们的部署将用来连接配置的内容。
将ConfigMap应用到集群中。
yml
kubectl apply -f ./database/postgres-config.yml
创建一个持久卷和持久卷声明
由于Pod是短暂的,也就是说一旦它们消失,它们所携带的所有数据也会消失,因此我们需要为我们的数据库提供持久存储。在这里,我们将定义容量、访问方式和卷的主机路径。
一旦我们创建了卷,我们需要一个 PersistantVolumeClaim
,它定义了用户请求和使用PV资源的方式。
创建一个名为 postgres-pvc-pv.yml
的新YAML文件。
yml
kind: PersistentVolume
apiVersion: v1
metadata:
name: postgres-pv-volume # Sets PV's name
labels:
type: local # Sets PV's type to local
app: postgres-db
spec:
storageClassName: manual
capacity:
storage: 5Gi # Sets PV Volume
accessModes:
- ReadWriteMany
hostPath:
path: "/mnt/data"
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: postgres-pv-claim # Sets name of PVC
labels:
app: postgres-db
spec:
storageClassName: manual
accessModes:
- ReadWriteMany # Sets read and write access
resources:
requests:
storage: 5Gi # Sets volume size
将PV和PVC应用于集群。
yml
kubectl apply -f ./database/postgres-pvc-pv.yml
创建服务
现在,我们只需创建一个简单的NodePort服务,并暴露端口5432。
创建一个名为 database-kube-app.yml
的新YAML文件。
yml
apiVersion: v1
kind: Service
metadata:
name: postgres # Sets service name
labels:
app: postgres-db # Labels and Selectors
spec:
type: NodePort # Sets service type
ports:
- port: 5432 # Sets port to run the postgres application
selector:
app: postgres-db
---
创建部署
最后,我们将为我们的Postgres数据库创建部署选项(在同一个文件中)。
yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-db-deployment # Sets Deployment name
spec:
replicas: 1
selector:
matchLabels:
app: postgres-db
template:
metadata:
labels:
app: postgres-db
spec:
containers:
- name: postgres-db
image: postgres:10.1 # Sets Image
imagePullPolicy: "IfNotPresent"
ports:
- containerPort: 5432 # Exposes container port
envFrom:
- configMapRef: # Maps env variable from ConfigMap
name: postgres-config
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-volume
volumes:
- name: postgres-volume
persistentVolumeClaim:
claimName: postgres-pv-claim # Maps claim from PersistantVolumeClaim
我们可以从配置和持久卷声明(这就是为什么名称和标签很重要!)中映射我们的环境变量 envFrom
和卷。
将服务和部署应用到集群中。
yml
kubectl apply -f ./database/postgres-pvc-pv.yml

测试数据库连接
为了保险起见,让我们测试一下,看看我们能否连接到数据库。
yml
kubectl exec -it [pod-name] -- psql -h localhost -U admin --password -p 5432 postgresdb
输入数据库密码并输入 \l
以列出所有数据库。

我们进来了!
成功!
恭喜!我们刚刚成功部署了一个高可用性和容错性的应用基础架构,使用了Kubernetes!Kubernetes和容器化应用的美妙之处在于,我们可以将其打包并在任何环境中导入,几乎不需要进行任何修改。
删除
完成后,我们可以运行 minikube stop
来停止环境,或者运行 minikube delete
来删除所有资源。