一、前言

为什么还学Docker的容器编排?

kubernetes几年前就是容器编排的龙头老大了,感觉上想学容器编排,是不是可以直接去学学k8s了呢?

其实我是学了一阵k8s之后折回头实践使用一下Docker容器编排的,因为在学k8s的过程中难免总是和Docker的容器编排做对比。所以不学学Docker Swarm,怎么知道K8S才是最好用、最强大的容器编排工具呢?

所以整理笔记 记录实战Docker Compose和Docker Swarm

二、Docker Compose

2.1、简介

Docker Compose 是Docker提供的定义、运行多个Docker应用容器的工具,我们通过Docker Compose定义配置好应用服务后,通过一条简单的命令就能根据这个配置创建出配置中描述的容器。

Docker Compose可以运行在生产环境、测试环境和开发环境中。

使用Docker Compose需要有这三个基础的步骤:

  1. 使用Dockerfile定义你的应用环境。

  2. 编写docker-compose.yml 定义你的服务,目的是为了让Dockerfile定义的容器可以一起运行。

    version: '2.0'
    services: # 描述我们的应用
    web:
    build: .
    ports:
    - "5000:5000"
    volumes:
    - .:/code
    - logvolume01:/var/log
    links: # 在启动前先启动redis
    - redis
    redis:
    image: redis
    volumes: # 持久化相关挂载卷
    logvolume01: {}
  3. 执行docker-compose up命令,Compose就会开始运行你描述的整个app。

参考:官方文档:https://docs.docker.com/compose/

2.2、下载安装

# 将docker-compose下载安装到 /usr/local/bin 目录下
curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

# docker-compse是一个二进制可执行的文件,所以给他可执行权限
sudo chmod +x /usr/local/bin/docker-compose

2.3、小实验

在这个小实验中,我们将使用Docker Compose安装一个简单的Java Web应用。

Java Web使用SpringBoot极速构建,提供一个Restful方法如下:

每次调用它都将redis里面的key=money的值+1后返回~

编写Dockerfile,这个dockerfile可以让我们将应用都jar包打包成 docker image

编写docker compose。这个描述文件就是在告诉docker-compose按照什么都规则去启动构建容器。

将 Dockerfile、docker-compose.yml、jar包都上传到服务器上~

为什么这里写服务名,而不写ip?

因为docker容器启动后会随机分配给它ip,举个例子:比如你有两个镜像A、B,那先如果先启动A,那他的ip可能是:172.16.0.2 。再启动B他的ip就可能是 172.16.0.3。 反之:如果你先启动的是容器B,那他的ip可能是172.16.0.3,而不是我们一开始说的172.16.0.2

所以这里写服务名是一个明智的选择,因为如果我们在这里固定写一个ip的话,万一哪天这个容器挂了,被重新拉起之后他的ip变了,那我们的web应用岂不是找不到这个redis了?所以用不会服务名替换ip。很明智。

容器启动后,同属一个网络下(默认一般是桥接)的容器彼此能根据对方的name彼此ping通。

使用docker-compose时,他会为我们新创建一个虚拟网卡。app中的容器启动后都会加入这个虚拟网络中。

对这块知识有疑问,可以看看这篇笔记:https://www.cnblogs.com/ZhuChangwu/p/13689736.html

web项目的配置文件如下图:

redis的host并不是某一个ip哦,而是一个服务名~

构建启动:应用服务

这其实是一个比较激动人心的事情。可以想一下,原来学docker的时候,我们直接从docker-hub上下载镜像,然后: docker run 使用镜像。

后来我们学着通过volume将容器中应用的配置文件挂载到宿主机实现简单的定制化配置,然后docker run 启动容器

再后来我们学习了Dockerfile,自定义镜像,实现了将我们本地打包好的应用做成镜像,然后docker run 启动容器。

到现在,我们下载安装好docker后,不用手动下载任何镜像,只要编写Dockerfile描述我们打包好后到程序应该做成什么镜像,通过docker-compose.yml 描述组成app的各个容器(可以是Dockerfile描述的镜像、本地已经存在的镜像、或者远程仓库中的镜像)之间有什么依赖关系。谁先启动,谁后启动。然后执行 docker-compose up命令,一键部署app。

# 一键部署
[root@VM-0-6-centos myServer]# docker-compose up # docker-compose为我们创建了一叫做: myserver_default的网络
Creating network "myserver_default" with the default driver # 构建web模块时,发现他depend_on redis,所以先拉去redis镜像
Pulling redis (redis:)...
latest: Pulling from library/redis
d121f8d1c412: Pull complete
2f9874741855: Pull complete
d92da09ebfd4: Pull complete
bdfa64b72752: Pull complete
e748e6f663b9: Pull complete
eb1c8b66e2a1: Pull complete
Digest: sha256:1cfb205a988a9dae5f025c57b92e9643ec0e7ccff6e66bc639d8a5f95bba928c
Status: Downloaded newer image for redis:latest
# 开始构建docker-compose.yml中指定的web模块
Building web
Step 1/5 : FROM java:8
8: Pulling from library/java
5040bd298390: Pull complete
fce5728aad85: Pull complete
76610ec20bf5: Pull complete
60170fec2151: Pull complete
e98f73de8f0d: Pull complete
11f7af24ed9c: Pull complete
49e2d6393f32: Pull complete
bb9cdec9c7f3: Pull complete
Digest: sha256:c1ff613e8ba25833d2e1940da0940c3824f03f802c449f3d1815a66b7f8c0e9d
Status: Downloaded newer image for java:8
---> d23bdf5b1b1b
Step 2/5 : COPY *.jar /app.jar
---> 15421d081257
Step 3/5 : EXPOSE 8888
---> Running in 18f52319a82d
Removing intermediate container 18f52319a82d
---> 86cb853b5711
Step 4/5 : CMD ["--server.port=8888"]
---> Running in 147d797d5848
Removing intermediate container 147d797d5848
---> 323a85aa9c61
Step 5/5 : ENTRYPOINT ["java","-jar","/app.jar"]
---> Running in 6988142d65d4
Removing intermediate container 6988142d65d4
---> 245e7675226d Successfully built 245e7675226d
Successfully tagged myserver_web:latest
WARNING: Image for service web was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. # 为我们的webapp成功创建了两个容器
Creating myserver_redis_1 ... done
Creating myserver_web_1 ... done
Attaching to myserver_redis_1, myserver_web_1 # 先启动redis
redis_1 | 1:C 21 Sep 2020 12:27:54.827 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1 | 1:C 21 Sep 2020 12:27:54.827 # Redis version=6.0.8, bits=64, commit=00000000, modified=0, pid=1, just started
redis_1 | 1:C 21 Sep 2020 12:27:54.827 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis_1 | 1:M 21 Sep 2020 12:27:54.828 * Running mode=standalone, port=6379.
redis_1 | 1:M 21 Sep 2020 12:27:54.828 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
redis_1 | 1:M 21 Sep 2020 12:27:54.828 # Server initialized
redis_1 | 1:M 21 Sep 2020 12:27:54.828 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
redis_1 | 1:M 21 Sep 2020 12:27:54.828 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').
redis_1 | 1:M 21 Sep 2020 12:27:54.828 * Ready to accept connections # 启动SpringBoot
web_1 |
web_1 | . ____ _ __ _ _
web_1 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
web_1 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
web_1 | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
web_1 | ' |____| .__|_| |_|_| |_\__, | / / / /
web_1 | =========|_|==============|___/=/_/_/_/
web_1 | :: Spring Boot :: (v2.3.4.RELEASE)
web_1 |
web_1 | 2020-09-21 12:27:56.777 INFO 1 --- [ main] com.changwu.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT on b66d5414a322 with PID 1 (/app.jar started by root in /)
web_1 | 2020-09-21 12:27:56.780 INFO 1 --- [ main] com.changwu.DemoApplication : No active profile set, falling back to default

服务启动后可以验证一下 docker-compose up命令是否构建起了我们的服务

停止服务:

  • 在yml文件所在目录执行: docker-compose stop
  • CTRL + C

docker-compose up 运行起所有的容器后,我们得到的是一个project,注意这里的这个project依然是一个单机的应用,相对于拆分前来说,现在的project是一个容器化后的单机应用。 (Docker提供的集群化部署方案在下面的章节~)

官方Demo:https://docs.docker.com/compose/gettingstarted/

2.4、小实验的细节

docker-compose up 成功执行后,可以看到他根据我们配置文件描述,为我们自动下载了redis镜像、java镜像、已经根据Dockerfile创建镜像

查看当前正在运行的容器(通过 docker-compose up 为我们自动运行起来的容器):

正在运行的容器的命名规则:目录名_镜像名_副本数

docker-compose up 命令还为我们创建了一个叫做 composetest_default的网络,整个项目中的容器都加入到这个网络中,这个网络支持我们使用 服务名访问到容器。是实现负载均衡的前提。

查看网络的详情:下面的两个容器在同一个网络下,

在同一个网络下的容器,彼此是可以通过对方的服务名访问到对方,如下:

2.5、Compose file的编写规则

参考:https://docs.docker.com/compose/compose-file/

三、Docker Swarm

3.1、简介:

Docker Engine 1.12诞生了Swarm。Docker的Swarm让我们可以通过一个或者多个Docker Engine组建起一个集群,没错Swarm就是Docker公司退出的集群编排工具。

使用docker swarm构建起的集群架构如下图:

主要存在两种角色:Manager节点和Worker节点,Manager和Woker本质上也都是Docker容器。

什么是Node?

大家都在说一个集群由多个节点组成,这个Node究竟是什么呢?

其实可以把这个节点理解成下载有Docker软件的服务器,通过Docker Swarm 会将我们启动的容器运行在某个Node中的Docker里面。

然后我感觉也可以把Node直接理解成某个服务器上运行的Docker实例。意思是你可以在一个服务器上启动多个Docker软件。当然这其实就和服务容器化以及分布式部署追求的那种容错性有出入~,毕竟都放在一个服务器上,万一服务器挂了,所有Docker实例,所有容器也都挂了。

Manager:

Manager主要掌控集群的管理任务

  • 维持集群的状态
  • 服务的调度(所谓调度就是通过一定的算法,让容器在合适的Node上启动起来)

manager之间彼此通信,我们针对整个集群的操作都要通过manager节点下发。

Worker:

worker节点是swarm集群中普通节点,他们会被Docker Swarm调度Woker节点上面运行起用户指定的容器。

参考:https://docs.docker.com/engine/swarm/

3.2、注意点:

我们使用Docker Swarm做了什么?

不要忽略一件事,使用docker swarm 为我们提供的命令,归根结底是为了搭建起一个swarm 集群,往这个集群中添加Node,从这个集群中砍掉Node。

搭建起集群之后下一步才是使用集群,使用集群使用的是另一套命令: docker service

3.3、环境搭建:

初始环境

查看docker swarm 命令:

初始化一个Docker Swarm 集群

通过 --advertise-addr 指定的ip可以是公网ip,也可以是私网ip。

如何将一个普通Node加入到一个集群中?

然后当我们成功初始化一个集群时,他会提示我们一条docker swarm join --token命令,通过这个命令我们可以让一个Node加入到以当前节点为Manager的去群中。

也可以该命令生成加入令牌:docker swarm join-token worker

如何查看当前集群各个节点的状态?

在manager节点执行如下命令:

在普通节点执行 docker node ls 会报错

如何将一个Manager Node加入到一个集群中?

使用该命令获取加入令牌:docker swarm join-token manager

该命令只能从manager节点使用。

创建swarm集群后,swarm为我们自动创建了新的网络~

3.4、Raft一致性协议

manager节点组成的集群使用了Raft算法,要求集群中多数以上的节点存活,集群才可以使用。他的本意是想让集群中存在3台及以上的manager,这样即使有manager挂了,整个集群依然是不影响使用的。

因此:如果你只有一个mananger节点,那集群就是不可用,你也不能通过manager下发任何任务。如果你有三台manager,即使挂了一个manager,因为还有半数以上的manager存在,集群依然可用。如果你说我的集群中就有两个manager,可不可用呢?答案是:可用,这种情况和集群中有三个manager,然后挂了一个manager一样,但是没意义。为啥呢?因为他根本没有任何容错性。再挂一个manager,集群就不可用了。

下一篇笔记,就好好研究一下Reft协议

3.5、弹性扩容、缩容

创建服务的命令:

通过docker service启动一个服务

只有在manager节点上才能使用这个命令。

docker run 和这个docker service挺相似的,但是通过docker service启动容器具有扩容、缩容的能力

查看启动的容器

思考:

我们在集群中的manager节点通过上面的命令启动一个服务。这其实就是Docker中容器的调度,或者也能说它是简单的编排。docker service create 命令本意上是在集群中启动一个nginx服务。这个nginx服务也就是一个docker容器,这个docker容器,会被manager随机在四个Node中的某一个Node上启动起来!

具体是哪个Node? 可以登陆上这几个服务器,通过 docker ps 查

动态扩容:

如下,增加我们的nginx的容器的副本数

对于WebServer来无论它访问哪台服务器的ip都能访问到nginx,而且无论这台服务器上的Docker中是否运行中nginx服务。docker swarm对内部的容器运行做了一层屏障,让很多服务以一个整体的形式存在。

理解:服务的概念

这里只是在表象上理解一下容器的概念~不涉及底层实现,但是有助于捋清思路。

我们在上面通过 docker service 命令启动一个服务,什么服务呢,通过--name指定一个叫做 mynginx的服务 ,服务中运行的镜像为nginx,如果本地没有这个镜像,他会去远程拉取。服务启动后,docker swarm会在集群中随机找一个合适的Node运行我们容器,这个容器属于我们这个 mynginx服务。

后来我们又用 docker service update 增加服务的副本数,就比如我们上面将服务的副本数增加到3,docker swarm就会让整个集群中再找合适的Node,然后启动新的service。

如下图,WebServer去连接Redis服务,这个Redis服务实际上就是存在于由Docker Swarm搭建起的集群中的Redis服务,并且它可能有多个副本。

对于WebServer来说,他不知道DockerSwarm集群中有多少服务的副本,对他来说,它只是在连接一个Redis实例,而不知道它连接的实际上是一个多分片集群。

Docker Swarm集群为WebServer提供Redis服务使用,WebServer可以任务Swarm集群是一个实例,然后WebServer只需要使用Swarm集群为他提供的Redis服务,而不关心Swarm集群中有多少个服务副本。

动态缩容:

动态缩容,就是通过 --replicas 指定一个比原来副本数小的数量即可

动态扩缩容的另一个命令:

移除服务:

服务被移除后,所以的服务副本都会消失,所有的相关容器也会被关闭

3.6、使用的感受

感觉上 Docker Swarm的调度设计是容器为核心的。

为啥这样说呢?

从上面的使用上来看,首先是拆解App,于是我们将应用拆分成不同容器。那不同容器之间是需要通过网络通信的,相应的DockerSwarm的实现是:Docker Swarm集群会构建一个igress网络(它是一个拥有负载均衡能力的overlay网络)所有的Node都加入到这个扁平化网络中,实现了网络中的各个Node直接通过对方的服务名就能ping通互联。

有了这样的基础后,我们就能使用Docker Swarm提供的扩容、所容的调度能力

当我们需要Docker Swarm为我们扩容时,它会根据自己的调度算法去找一个合适的Node然后去一个一个的运行起我的容器,也就是说它的思路是:为容器找一个合适的节点运行起来

spread: 默认策略,尽量均匀分布,找容器数少的结点调度

binpack: 和spread相反,尽量把一个结点占满再用其他结点

random: 随机

这种调度思想和k8s的编排思想是不一样的。

当然说起k8s难免会冒出海量的新概念,这里就不展开了。

简单来说:kubernates的编排思想是这样的,它根本不关系底层的容器是Docker还是其他的容器技术,它站在更高的角度上,允许让用户以yaml的方式去描述自己的应用,去描述哪几个镜像启动后应在绑定在一起(在一个Pod里,而不是像Docker Swarm那样,由Docker Swarm去为单个容器找一个合适的Node),允许用户指定Pod的数量,还为用户提供了 网关、监控、备份、水平扩展、滚动更新、保持指定数量的副本数、负载均衡等功能。

实战Docker容器调度的更多相关文章

  1. 如何设置Docker容器中Java应用的内存限制

    如果使用官方的Java镜像,或者基于Java镜像构建的Docker镜像,都可以通过传递 JAVA_OPTS 环境变量来轻松地设置JVM的内存参数.比如,对于官方Tomcat 镜像,我们可以执行下面命令 ...

  2. ASP.NET Core 实战:使用 Docker 容器化部署 ASP.NET Core + MySQL + Nginx

    一.前言 在之前的文章(ASP.NET Core 实战:Linux 小白的 .NET Core 部署之路)中,我介绍了如何在 Linux 环境中安装 .NET Core SDK / .NET Core ...

  3. 容器调度 • Docker网络 • 持续交付 • 动态运行应用程序 部署的多元化

    <英雄联盟>在线服务运维之道 - InfoQ https://www.infoq.cn/article/running-online-services-riot/ 第一章 简 介 我是Jo ...

  4. docker容器实战-----初级<2>

    第二章  docker容器 1. Docker是通过内核虚拟化技术(namespaces及cgroups cpu.内存.磁盘io等)来提供容器的资源隔离与安全保障等.由于Docker通过操作系统层的虚 ...

  5. Docker实战(二)之操作Docker容器

    容器是Docker的另外一个核心概念.简单来说,容器是镜像的一个运行实例.所不同的是,镜像是静态的只读文件,而容器带有运行时需要的可写文件层.如果认为虚拟机是模拟运行的一整套操作系统系统(包括内核,应 ...

  6. 最佳实战Docker持续集成图文详解

    最佳实战Docker持续集成图文详解 这是一种真正的容器级的实现,这个带来的好处,不仅仅是效率的提升,更是一种变革:开发人员第一次真正为自己的代码负责——终于可以跳过运维和测试部门,自主维护运行环境( ...

  7. Kubernetes(k8s)集群部署(k8s企业级Docker容器集群管理)系列之集群部署环境规划(一)

    0.前言 整体架构目录:ASP.NET Core分布式项目实战-目录 k8s架构目录:Kubernetes(k8s)集群部署(k8s企业级Docker容器集群管理)系列目录 一.环境规划 软件 版本 ...

  8. docker容器入门最佳教程

    为什么要写这个 简单回答是:容器技术非常热门,但门槛高. 容器技术是继大数据和云计算之后又一炙手可热的技术,而且未来相当一段时间内都会非常流行. 对 IT 行业来说,这是一项非常有价值的技术.而对 I ...

  9. 6.Docker容器底层实现了解与安全机制

    原文地址: 点击直达 0x00 底层实现 我们以 Docker 基础架构来探究Docke底层的核心技术,简单的包括: Linux 上的命名空间(Namespaces) 控制组(Control grou ...

随机推荐

  1. 进阶6:连接查询 一、sql92标准

    #进阶6:连接查询/*含义:又称多表查询,当查询的字段来自于多个表时,就会用到连接查询 笛卡尔乘积现象:表1 有m行,表2有n行,结果=m*n行 发生原因:没有有效的连接条件如何避免:添加有效的连接条 ...

  2. nautilus pg autoscaler PG自动伸缩

    链接地址:https://ceph.io/rados/new-in-nautilus-pg-merging-and-autotuning/ [root@controller ~]# ceph osd ...

  3. 下载 golang.org/x 包出错不用代理的解决办法

    原文链接:https://www.jianshu.com/p/6fe61053c8aa?utm_campaign=maleskine&utm_content=note&utm_medi ...

  4. 10款人气暴涨的PHP开源工具

    若想创建动态而又新颖的Web应用程序,PHP便是理想的选择.不用说,在Web开发世界里,PHP是最流行的语言之一.一些非常好用的PHP开源工具着实拯救了不少开发任务繁重的PHP开发人员,减轻他们的开发 ...

  5. spss如何把多个指标合并成一个变量?

    把多个指标合并成一个变量,通常有两种做法: 一.计算平均值 针对问卷量表数据,同时几个题表示一个维度.比如想要将“我在工作中能获得成就感”.“我可以在工作中发挥个人的才能”这两题合并成一个维度(影响因 ...

  6. 区块链入门到实战(19)之以太坊(Ethereum) – 以太币

    以太币的作用:防范以太坊网络被滥用和激励矿工. 与比特币网络有比特币类似,以太坊(Ethereum)也有自己的虚拟币 — 以太币. 以太币的主要作用有2个: 应用程序执行任何操作都需要支付以太币,防范 ...

  7. OMG,12 个精致的 Java 字符串操作小技巧,学它

    字符串可以说是 Java 中最具有代表性的类了,似乎没有之一哈,这就好像直播界的李佳琪,脱口秀中的李诞,一等一的大哥地位.不得不承认,最近吐槽大会刷多了,脑子里全是那些段子,写文章都有点不由自主,真的 ...

  8. Http请求的三个常见问题

    我们做的大多数项目,必不可少的需要向后台发送请求获取数据,常用的http请求就是post请求和get请求 那么引出一个最常见的问题——Q:post请求和get请求有什么区别? A: 从语义上我们可以这 ...

  9. JAVA虚拟机故障诊断总结

    一.JAVA运行时数据区               1.堆(-Xmx与-Xms):所有线程共享.  目的:用来存放对象实例.所有对象实例和数组都要在堆上分配内存.JAVA堆是垃圾收集器管理的主要区域 ...

  10. js中的各种常用方法(持续更新中。。。)

    我看到常用的就写上去,如果你们有,可以在评论上发表,我再把它补充到我的随笔中 some() var ages = [3, 10, 18, 20]; function checkAdult(age) { ...