avatar

「docker&k8s」容器编排

docker-compose

什么是docker-compose

  1. 我们知道使⽤⼀个 Dockerfile 模板⽂件,可以让⽤户很⽅便的定义⼀个单独的应⽤容器。然⽽,在⽇常⼯作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现⼀个 Web 项⽬,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚⾄还包括负载均衡容器等,我们只能一个一个写dockerfile文件,然后bulid构建,run手动操作单个容器Compose 恰好满⾜了这样的需求。它允许⽤户通过⼀个单独的 docker-compose.yml 模板⽂件(YAML 格式)来定义⼀组相关联的应⽤容器为⼀个项⽬(project)

    Docker Compose来轻松高效的管理容器,定义运行多个容器。

  2. Compose 中有两个重要的概念
    • 服务 ( service ):
      ⼀个应⽤的容器,实际上可以包括若⼲运⾏相同镜像的容器实例(web、redis、mysql …)。
    • 项⽬ ( project ):
      由⼀组关联的应⽤容器组成的⼀个完整业务单元,在 docker-compose.yml ⽂件中定义(博客、web、mysql)。

      启动docker-compose up 可以启动多个服务

插件安装


根据官方文档,我们直接用yum安装docker-compose插件
yum install docker-compose-plugin
安装完成后用docker compose -v验证安装

当然也可以选择独立安装的方式,这里不做演示
参考Install the Compose standalone

Compose初体验

通过官方案例来演示:https://docs.docker.com/compose/gettingstarted/

创建对应的目录

1
2
mkdir composetest
cd composetest

创建Python文件 app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)

def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)

@app.route('/')
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count)

在同级目录下创建requirements.txt文件
flask
redis

然后创建对应的Dockerfile文件

1
2
3
4
5
6
7
8
9
10
11
# syntax=docker/dockerfile:1
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]

然后创建核心的 yml文件docker-compose.yml

1
2
3
4
5
6
7
8
9
10
version: "3"
services:
web:
build: .
ports:
- "5000:5000"
depends_on:
- redis
redis:
image: "redis:alpine"

最终通过docker-compose up命令来启动容器

Docker-Compose.yml配置详解

Docker-Compose.yml标准配置文件应该包含 version、services、networks 三大部分,其中最关键的就是 services 和 networks 两个部分.

详细说明可以参考
docker compose 配置文件 .yml 全面指南

docker-swarm

什么是docker-swarm

Docker Swarm 和 Docker Compose 一样,都是 Docker 官方容器编排项目,但不同的是,Docker Compose 是一个在单个服务器或主机上创建多个容器的工具,而 Docker Swarm 则可以在多个服务器或主机上创建容器集群服务,对于微服务的部署,显然 Docker Swarm 会更加适合。

Swarm的几个关键概念

1.Swarm
集群的管理和编排是使用嵌入docker引擎的SwarmKit,可以在docker初始化时启动swarm模式或者加入已存在的swarm

2.Node
一个节点是docker引擎集群的一个实例。您还可以将其视为Docker节点。您可以在单个物理计算机或云服务器上运行一个或多个节点,但生产群集部署通常包括分布在多个物理和云计算机上的Docker节点。
要将应用程序部署到swarm,请将服务定义提交给 管理器节点。管理器节点将称为任务的工作单元分派 给工作节点。
Manager节点还执行维护所需群集状态所需的编排和集群管理功能。Manager节点选择单个领导者来执行编排任务。
工作节点接收并执行从管理器节点分派的任务。默认情况下,管理器节点还将服务作为工作节点运行,但您可以将它们配置为仅运行管理器任务并且是仅管理器节点。代理程序在每个工作程序节点上运行,并报告分配给它的任务。工作节点向管理器节点通知其分配的任务的当前状态,以便管理器可以维持每个工作者的期望状态。

3.Service
一个服务是任务的定义,管理机或工作节点上执行。它是群体系统的中心结构,是用户与群体交互的主要根源。创建服务时,你需要指定要使用的容器镜像。

4.Task
任务是在docekr容器中执行的命令,Manager节点根据指定数量的任务副本分配任务给worker节点

docker swarm:集群管理,子命令有init, join, leave, update。(docker swarm –help查看帮助)
docker node:节点管理,子命令有accept, promote, demote, inspect, update, tasks, ls, rm。(docker node –help查看帮助)
docker service:服务创建,子命令有create, inspect, update, remove, tasks。(docker service–help查看帮助)

node是加入到swarm集群中的一个docker引擎实体,可以在一台物理机上运行多个node,node分为:
manager nodes,也就是管理节点
worker nodes,也就是工作节点

1)manager node管理节点:执行集群的管理功能,维护集群的状态,选举一个leader节点去执行调度任务。
2)worker node工作节点:接收和执行任务。参与容器集群负载调度,仅用于承载task。
3)service服务:一个服务是工作节点上执行任务的定义。创建一个服务,指定了容器所使用的镜像和容器运行的命令。
service是运行在worker nodes上的task的描述,service的描述包括使用哪个docker 镜像,以及在使用该镜像的容器中执行什么命令。
4)task任务:一个任务包含了一个容器及其运行的命令。task是service的执行实体,task启动docker容器并在容器中执行任务。

docker swarm 集群搭建

在创建集群之前,我们使用docker node ls查看下集群中节点的信息,反馈目前没有节点信息,并且当前节点并不是manager。

下面我们使用docker swarm init命令在manager这个节点上对集群进行初始化操作,将它设置为manager,并且设置自己的通讯IP为172.16.171.180

可以看到集群已经创建成功了。并得到如下信息:

  • 可以在其它节点上执行docker swarm join-token manager来获取下一步执行指令,执行该指令后,该节点将设置为manager从节点加入到这个swarm集群中;

我们只要复制它给出的命令在node节点上执行,便可加入集群

这就加入成功了,我们docker node list查看一下集群状态

如此,一个swarm集群就算搭建完成了。

manager是我们管理集群的入口,我们的docker命令都是在manager上执行,node节点上是不能执行dockr命令的,这一点要十分牢记。

docker swarm 集群使用

手动创建(docker service)

这里我用katacoda/docker-http-server这个镜像作为演示
在管理节点上执行,创建拥有2个副本的服务
docker service create -p 8001:80 --replicas 2 katacoda/docker-http-server:latest

docker service ps [OPTIONS] SERVICE [SERVICE...]查看服务运行状态

可以看到服务已经在节点上运行了,我们使用curl去获取页面

我们访问集群内的任意节点,swarm都会把我们的请求均衡的分发到集群内的各节点响应。
有关swarm负载均衡的详解可以参考————Docker Swarm - 服务发现和负载均衡原理

这又回到了古老的docker run, 有没有类似docker compose的东西呢?

Docker stack编排

什么是Docker stack?

前面我们学习了Docker Compose,它是用来进行一个完整的应用程序相互依赖的多个容器的编排的,但是缺点是不能在分布式多机器上使用,我们也介绍了Docker swarm,它构建了docker集群,并且可以通过docker service在不同集群节点上运行容器服务,但是缺点是不能同时编排多个服务,但是在实际的生产开发中,我们一个完整的应用需要的服务往往不止一个,通过docker service 命令来部署的话会很麻烦,为了解决这个问题,这里我们需要学习一个新的命令集docker stack,它用于向swarm集群部署完整的应用程序堆栈,可以在分布式多机器上同时编排多个有依赖关系的服务。Stack 能够在单个声明文件中定义复杂的多服务应用,还提供了简单的方式来部署应用并管理其完整的生命周期:初始化部署 -> 健康检查 -> 扩容 -> 更新 -> 回滚,以及其他功能!可以简单地理解为Stack是集群下的Compose。

Docker Stack 编排依赖于声明文件,其实也就是Docker Compse文件,不过Stack对于Compose文件有一个要求,文件的规范版本(顶层关键字version)必须是“3.0”或以上,而且一些Docker Compse文件中的关键字不受支持,如:

  • build
  • cgroup_parent
  • container_name
  • devices
  • tmpfs
  • external_links
  • links
  • network_mode
  • restart
  • security_opt
  • userns_mode

由于 build 关键字在 Stack 中不受支持,不能在编排的过程中构建镜像,docker stack部署用到的镜像必须是已经构建发布好并且发布到 docker 仓库的,在服务编排过程各个节点中直接从 docker 仓库拉取。

浅浅试一下docker stack

我们还是采用上文flask+redis的例子演示。
build 关键字在 Stack 中不受支持,所以我们先手动build一下web服务的image(ps:Dockerfile见上文)
docker build . -t stack-web

修改一下docker-compose.yml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "3"
services:
web:
image: "stack-web:latest"
ports:
- "8000:5000"
depends_on:
- redis
deploy:
replicas: 2

redis:
image: "redis:alpine"
deploy:
replicas: 2

这里我把部署的副本数设为2,有关deploy参数下的各种配置可以参考docker-compose中的deploy

执行docker stack deploy -c docker-compose.yml stack-web部署栈

docker service ps stack-web_redis stack-web_web查看服务运行状态

这里由于我没有在overlay-2节点build image所以stack-web服务没办法在overlay-2节点上运行,自动迁移到了overlay-1节点

那要是有更多的应用怎么管理呢? 走!上k8s!

Kubernetes

k8s简介

kubernetes的本质是一组服务器集群,它可以在集群的每个节点上运行特定的程序,来对节点中的容器进行管理。目的是实现资源管理的自动化,主要提供了如下的主要功能:

  • 自我修复:一旦某一个容器崩溃,能够在1秒中左右迅速启动新的容器
  • 弹性伸缩:可以根据需要,自动对集群中正在运行的容器数量进行调整
  • 服务发现:服务可以通过自动发现的形式找到它所依赖的服务
  • 负载均衡:如果一个服务起动了多个容器,能够自动实现请求的负载均衡
  • 版本回退:如果发现新发布的程序版本有问题,可以立即回退到原来的版本
  • 存储编排:可以根据容器自身的需求自动创建存储卷

这不是和swarm也没啥区别嘛???

没错,swarm和k8s本质都是容器编排服务,但是:

  • 设计理念:swarm偏重的是容器的部署,而k8s更高一层:应用的部署
  • 应用场景:swarm与docker天然集成,安装和使用很简单,k8s围绕着应用的部署开发了很多组件,这些组件很多并不依赖于docker的api,在部署时需要单独规划和实施,而且因为组件中很多策略适应不同的部署场景
  • docker vs pod:在k8s中,最小单元则是podpod由一个或者多个为实现某个特定功能而放在一起的容器组成比较灵活,在swarm中,被创建、调度和管理的最小单元就是docker
  • 负载均衡:swarm自带的负载均衡机制应用不广,大部分还是采用nginx+consul。kubernetes的负载均衡要完善很多,内部集成了负载均衡
  • 灰度发布:k8s有replication controller的机制,可以人为控制灰度发布的过程,swarm的灰度发布不够灵活
  • 弹性伸缩:k8s可以根据Pod的使用率自动调整一个部署里面Pod的个数,保障服务可用性,但swarm则不具备这种能力。
  • 生态:swarm是docke官方推出的集群方案,k8s是脱胎于google的一款基于容器的应用部署和管理打造一套强大并且易用的管理平台。相比swarm而言,k8s更懂容器的管理。

kubernetes yyds! 就对了!

kubernetes概念

  • Master:集群控制节点,每个集群需要至少一个master节点负责集群的管控
  • Node:工作负载节点,由master分配容器到这些node工作节点上,然后node节点上的docker负责容器的运行
  • Pod:kubernetes的最小控制单元,容器都是运行在pod中的,一个pod中可以有1个或者多个容器
  • Controller:控制器,通过它来实现对pod的管理,比如启动pod、停止pod、伸缩pod的数量等等
  • Service:pod对外服务的统一入口,下面可以维护者同一类的多个pod
  • Label:标签,用于对pod进行分类,同一类pod会拥有相同的标签
  • NameSpace:命名空间,用来隔离pod的运行环境

kubernetes编排

😩我也不会k8s,但是今晚就要交作业了咋办?
让我们跟着这个RM船长3小时速通一下吧
Learn Kubernetes in Under 3 Hours: A Detailed Guide to Orchestrating Containers

这船长还是有坑的,排了好久错总算是吧这套服务搭完了,接下来我们来康康怎么通过k8s去编排

这里我就不演示排错的过程了,我已经打包好了一会要用到的镜像

大概的架构如图
我们先通过Deployment部署SA-Logic,配置文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: apps/v1
kind: Deployment
metadata:
name: sa-logic
labels:
app: sa-logic
spec:
selector:
matchLabels:
app: sa-logic
replicas: 2
minReadySeconds: 15
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: sa-logic
spec:
containers:
- image: lixingyu/sentiment-analysis-logic:latest
imagePullPolicy: Never
name: sa-logic
ports:
- containerPort: 5000

执行kubectl create -f sa-logic-deployment.yaml部署

当然我们还要创建对应的服务,配置文件如下

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Service
metadata:
name: sa-logic
spec:
ports:
- port: 5000
protocol: TCP
targetPort: 5000
selector:
app: sa-logic

执行kubectl create -f service-sa-logic.yaml部署

同样的编写 web-app 的部署文件和服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: apps/v1
kind: Deployment
metadata:
name: sa-web-app
labels:
app: sa-web-app
spec:
selector:
matchLabels:
app: sa-web-app
replicas: 2
minReadySeconds: 15
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: sa-web-app
spec:
containers:
- image: lixingyu/sentiment-analysis-web-app
imagePullPolicy: Never
name: sa-web-app
env:
- name: SA_LOGIC_API_URL
value: "http://sa-logic:5000"
ports:
- containerPort: 8080

服务配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: sa-web-app-lb
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 8080
nodePort: 8080
selector:
app: sa-web-app

通过kubectl create -f命令部署

最后部署前端服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: apps/v1
kind: Deployment
metadata:
name: sa-frontend
labels:
app: sa-frontend
spec:
selector:
matchLabels:
app: sa-frontend
replicas: 2
minReadySeconds: 15
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: sa-frontend
spec:
containers:
- image: lixingyu/sa-frontend-2
imagePullPolicy: Never
name: sa-frontend
ports:
- containerPort: 80

服务配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: sa-frontend-lb
spec:
type: NodePort
ports:
- port: 9999
name: test
targetPort: 80
nodePort: 8088
selector:
app: sa-frontend

我直接一把梭

最后通过kubectl get deployments.appskubectl get service这两个命令检查一下各服务的状态

通过web访问

总算是整完了

配置了这么多奇奇怪怪的文件,它们又是干嘛的呢?

kubernetes配置详解

具体参考K8S中 yaml 文件详解这篇文章
遇到不懂的,善用kubectl explain命令查看

基本配置都可以在导出模板的基础上修改kubectl create deployment [] --image [] -o yaml --dry-run

–dry-run 表示只是测试资源而不会真正创建~

有关service 的type

k8s支持的4种类型的Service分别是:

  • ClusterIP
  • NodePort
  • LoadBalancer
  • ExternalName
    下面我们详细康康
    ClusterIP (默认)
    类型为ClusterIP的service,有一个Cluster-IP,其实就一个VIP。具体实现原理依靠kubeproxy组件,通过iptables或是ipvs实现。
    这种类型的service 只能在集群内访问。
    配置文件如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    apiVersion: v1
    kind: Service
    metadata:
    name: service-python
    spec:
    ports:
    - port: 3000
    protocol: TCP
    targetPort: 443
    selector:
    run: pod-python
    type: ClusterIP
    NodePort
    我们的场景不全是集群内访问,也需要集群外业务访问。那么ClusterIP就满足不了了。NodePort当然是其中的一种实现方案。
    我认为有点类似与docker上的端口映射
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    apiVersion: v1
    kind: Service
    metadata:
    name: service-python
    spec:
    ports:
    - port: 3000 #集群内部端口
    protocol: TCP
    targetPort: 443 #容器端口
    nodePort: 30080 #外部端口(1024-65535)
    selector:
    run: pod-python
    type: NodePort
    LoadBalancer(公有云才会用到)
    LoadBalancer类型的service 是可以实现集群外部访问服务的另外一种解决方案。不过并不是所有的k8s集群都会支持,大多是在公有云托管集群中会支持该类型。负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过Service的status.loadBalancer字段被发布出去。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    apiVersion: v1
    kind: Service
    metadata:
    name: service-python
    spec:
    ports:
    - port: 3000
    protocol: TCP
    targetPort: 443
    nodePort: 30080
    selector:
    run: pod-python
    type: LoadBalancer
    ExternalName(不常用)
    类型为 ExternalName 的service将服务映射到 DNS 名称,而不是典型的选择器,例如my-service或者cassandra。您可以使用spec.externalName参数指定这些服务。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    kind: Service
    apiVersion: v1
    metadata:
    name: service-python
    spec:
    ports:
    - port: 3000
    protocol: TCP
    targetPort: 443
    type: ExternalName
    externalName: remote.server.url.com

    总结

    k8s yyds 睡了睡了😭

文章作者: Sunset
文章链接: https://pwnedyu.cn/2022/11/29/%E3%80%8Cdocker&k8s%E3%80%8D%E5%AE%B9%E5%99%A8%E7%BC%96%E6%8E%92/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Sunset's blog
打赏
  • 微信
    微信
  • 支付寶
    支付寶