CoreOS实践指南(七):Docker容器管理服务

Stella981
• 阅读 665

这次的主角终于轮到了大鲸鱼Docker。不晓得有多少人是因为Docker认识了CoreOS的,至少它在社区的知名度事实上高于CoreOS项目本身。这篇文章里不会对Docker做很深入的讲解,而重点放在开始使用Docker所需的基本知识以及在CoreOS中使用Docker托管服务的推荐实践方法。 

CoreOS实践指南(七):Docker容器管理服务  

结缘

雷教主说,“站在风口上,猪也能飞起来”。Docker正是借着云计算的风飞上了天。伴随着Docker和应用容器的兴起,拉动了一批PaaS产品的发展,而CoreOS也借了这股劲儿赚足了人气,进行得风生水起。同时CoreOS的成熟也在回馈Docker社区,为社区带来了例如Etcd、Deis(私有PaaS云平台,目前是基于CoreOS构建的)等许多新的活力。

说起CoreOS与Docker的渊源,确有一段历史了。故事大致是这样开始的,2013年2月,美国的dotCloud公司发布了一款新型的Linux容器软件Docker,并建立了一个网站发布它的首个演示版本(见Docker第一篇官方博客)。而几乎同时,2013年3月,美国加州,年轻的帅小伙Alex Polvi正在自己的车库开始他的 第二次创业。此前,他的首个创业公司Cloudkick卖给了云计算巨头Rackspcace(就是OpenStack的东家)。

有了第一桶金的Alex这次准备干一票大的,他计划开发一个足以颠覆传统的服务器系统的Linux发行版。为了提供能够从任意操作系统版本稳定无缝地升级到最新版系统的能力,Alex急需解决应用程序与操作系统之间的耦合问题。因此,当时还名不见经传的Docker容器引起了他的注意,凭着敏锐直觉,Alex预见了这个项目的价值,当仁不让地将Docker做为了这个系统支持的第一套应用程序隔离方案。不久以后,他们成立了以自己的系统发行版命名的组织:CoreOS。事实证明,采用Docker这个决定,后来很大程度上成就了CoreOS的生态系统。

现在看来,CoreOS已经不是唯一预装了Docker的操作系统了,但它是第一个,也是目前做得最成功的一个。RedHat和Canonical(Ubuntu的母公司)随其后也分别推出了自己的预装Docker的系统发行版,但知悉者寥寥,并没有做成气候。其项目发起时间见下图(出自成都ThoughtWorks技术雷达分享活动),Atomic和Ubuntu Core Snappy分别是RedHat和Canonical公司推出的预装Docker的操作系统,目标也都是直指服务器集群和容器化部署。

CoreOS实践指南(七):Docker容器管理服务

应用容器

“应用容器”现在对许多人已经并不陌生了。但它在服务器的系统上还不是那么普及,至少与你手上的智能手机系统相比。至今在服务器系统上流行的安装软件方式依然是编译源代码、手工的安装包或各种包管理工具,虽然包管理工具的出现解决了应用软件安装、卸载以及自身依赖等诸多问题,却无法很好的解决软件之间的依赖冲突。而早在Docker诞生以前,“沙盒”的概念已经被普遍使用在Android、iOS等主流的手机系统中了。通过沙盒的隔离,应用软件将自己所有的依赖与应用本身打包在一起,并通过SDK API提供的可控的方式访问操作系统,软件与系统的耦合度大大降低。这样带来的直接好处是,软件之间的依赖冲突得到了很好的解决,移除一个应用软件一般只需要很短的几秒钟并且彻底无痕,软件访问系统的安全性也更加可控。

事实上,Android实现沙盒同样的基于Linux内核的cgroup和namespace机制用于限制和隔离资源的使用,所使用的技术与Docker如出一辙。这些早在Linux 2.6.x版本就已经加入了的新特性,已经通过了较长时间的检验,被证实是可行并且可靠的。

当CoreOS 遇见 Docker

这篇文章里不会专门介绍Docker的使用,而是关注在具体的场景下,如何在CoreOS中恰当的管理Docker容器。了解过Docker在CoreOS生态系统中的角色后,下面通过在两个容器中分别运行NodeJS和MongoDB的例子说明如何在CoreOS中通过Systemd管理服务,并在此基础上快速浏览一些基本的Docker命令。

  • 制作服务的Docker镜像

服务镜像有一些可以是现成的标准服务的镜像,例如MongoDB服务。另一些则需要经过用户定制,制作Docker镜像文件一般可以通过Dockerfile或现有容器实例生成两种方法。前者是比较推荐的做法,但需学习Dockerfile的写法,已经超出了这个系列的范围。后者相对简单,但不利于后期的镜像维护管理,这里仅仅作为演示目的,因此采用这种方法。

一、拉取基础镜像

每一个具体的容器实际上是运行在虚拟出来的独立空间里面的,它被设计成只能够访问到存在于同一个虚拟空间下面的其他文件。因此为了使应用能够使用基本的运行时依赖,还需要将一些Linux的命令和配置文件也打包放到虚拟空间里,这种打包好的依赖文件集合就是镜像。

操作 docker 的方式与 systemctl、etcdctl 类似,需要由一个二级命令共同组成一个完整的命令。通过 docker pull  命令可以指定的网络地址拉取镜像到本地(如果指定的是名称而不是网络地址,则会在docker官方的镜像仓库里面搜索,比如下面的两个例子)。

$ docker pull node:latest
...
Status: Downloaded newer image for node:latest
$ docker pull mongo:latest
...
Status: Downloaded newer image for mongo:latest

镜像是按照“地址/镜像名:版本标签”格式命名的,其中镜像名是必须的,如果地址部分为空则默认为官方仓库地址。如果版本标签部分为空,对于较新的Docker版本(大约1.3.x以后),会仅仅下载标签为latest的版本,而较早版本的Docker则会下载指定镜像的所有版本,常常会因此意外下载许多不需要的镜像版本。

在一大段输出以后,若一切顺利(事实是,在国内可能不会太顺利),本地的Docker已经可以直接使用这两个预装了NodeJS和MongoDB的镜像了。可以通过 docker images 命令验证。

$ docker images
REPOSITORY  TAG  IMAGE ID  CREATED  VIRTUAL  SIZE
node  latest  61afc26cd88e  3 days ago  696.2 MB
mongo   latest  59b3d123f9b8  6 days ago  392.4 MB
...

在国内的一些地区,拉取官方镜像仓库的镜像可能会失败(或许是大名鼎鼎的某防火墙的功劳)。此时可以采用国内的第三方开源镜像仓库,比如DockerPoolDocker.cn提供的镜像文件。前者需要配置本地的SSL证书,否则会遇到“Error: Invalid registry endpoint”错误,略微麻烦。后者可以直接使用:

docker pull docker.cn/docker/node:latest
docker pull docker.cn/docker/mongo:latest

二、制作定制镜像

MongoDB可以直接使用官方的Docker镜像。而NodeJs的容器还需要些许定制,将应由部署到容器中然后生成新的镜像。再次说明,制作镜像的最佳途径是写一个Dockerfile,实现基础设施可视化。以下通过修改现有镜像的方法一般只用于演示目的。

接下来我们要分别启动MongoDB和NodeJs的容器实例,并将MongoDB的端口暴露到NodeJs的容器中。

首先启动一个MongoDB容器实例,命名为mongo-ins。启动容器的命令是 docker run,除了运行配置参数如  --name、--port 等,这个命令的最后两个参数分别是实例使用的镜像名字,和实例本身需要运行的命令。有的容器已经配置好了默认的运行程序,此时后面的一个参数可以省略,比如下面的例子。

参数 -d 表示运行后直接进入后台,屏幕上回显的一串输出是新启动容器实例的ID。

然后启动一个NodeJs容器实例,使用官方的node镜像作为基础镜像,并将它与 mongo-ins 实例建立“连接”。这个容器实例命名为node-app。

$ docker run --name node-app -p 3000 --link mongo-ins:mongo -it node /bin/bash
root@e73e7d7836a6:/#  <— 已经进入容器中的Bash>

-it 实际上是 -i -t 的简便写法,表示启用交互式模式和启用显示终端,这样我们可以进入容器中做一些手工操作。而参数 --link 用来将两个容器进行关联,关于Docker Link的用法可以参考Docker的相关文档。简单来说,Link的参数 mongo-ins:mongo 表示将容器 mongo-ins 引入到正在建立的容器镜像中,并将其称为 mongo。这样做的结果是,在新建的 node-app 容器实例中,能够访问到两个全局环境变量: $MONGO_PORT_27017_TCP_ADDR 和 $MONGO_PORT_27017_TCP_PORT,分别是用来访问 MongoDB 的 IP 地址和端口。

作为演示,我们将在容器中部署一个从Github获取的简单示例。

$ git clone  https://github.com/ijason/NodeJS-Sample-App.git $ cd /NodeJS-Sample-App/EmployeeDB
$ sed -i -e "s/27017/process.env.MONGO_PORT_27017_TCP_PORT/" -e "s/'localhost'/process.env.MONGO_PORT_27017_TCP_ADDR/" app.js
$ exit

上面的第三条命令将原本容器中指定的 MongoDB 位置改成了从另一个容器中暴露的IP地址和端口。至此这个node-app容器已经部署好了一个名为 Employees 的示例应用,接下来将它生成镜像并放到集群的每个节点上。

三、生成并提交镜像

为了在集群里对容器中的服务提供横向扩展能力,需要将定制好的容器在集群的所有节点共享。

首先需要一个存放共享镜像的地方,在企业环境可以使用私有的镜像仓库,但为了演示简便起见,我们直接使用Docker的公共仓库。首先需要在Docker Hub注册一个用户,然后使用 docker login 命令登陆到仓库服务器。

$ docker login
Username: linfan
Password:
Email: linfan@******.com
Login Succeeded

然后我们需要将本地修改过的容器使用 docker commit 命令生成一个本地的镜像。注意,由于之后需要将镜像提交至Docker Hub,这里镜像的名字必须以自己的Docker Hub用户名作为前缀,否则在后面的 push 时候会遇到 403 “Access Denied: Not allowed to create Repo at given location” 错误。例如名为 linfan/employees。

$ docker commit node-app linfan/employees
a4281aa8baf9aee1173509b30b26b17fd1bb2de62d4d90fa31b86779dd15109b
$ docker images
REPOSITORY  TAG  IMAGE ID  CREATED  VIRTUAL SIZE
linfan/employees  latest  a4281aa8baf9  14 seconds ago  696.2 MB

最后,使用 docker push 命令将这个准备好的镜像提交到Docker Hub仓库中。

$ docker push linfan/employees
The push refers to a repository [linfan/employees] (len: 1)
Sending image list
...
Pushing tag for rev [5577d6743652] on {https://cdn-registry-1.docker.io/v1/repositories/linfan/employees/tags/latest}

提交完成后,在其他节点就可以使用 docker pull 命令获取到这个镜像了。

注意:严格来说,将数据库服务容器通过Docker Link暴露给应用服务容器的方法并不符合分布式应用的12条准则,因为通过Docker Link连接的两个容器必须运行在同一个物理主机上,数据与应用不能在集群中分别独立的部署或横向扩展。

  • 使用 Fleet 启动服务容器

一、编写 Unit 文件

有了相应的服务容器后,在CoreOS中正确启动服务的方法应该是通过Fleet来管理。通过合理使用 Unit 的 X-Fleet 配置,能够很好的解决容器直接相互依赖的问题。

用 vagrant ssh 进入一个 CoreOS 的 Shell 中,创建以下两个服务 Unit 文件。

首先是mongo.service

[Unit]
Description=General MongoDB Service
After=docker.service
[Service]
TimeoutStartSec=0
ExecStart=/opt/bin/docker-run.sh --name mongo-ins -d mongo
ExecStop=/usr/bin/docker stop mongo-ins

然后是employees.service,请注意它的 Unit 和 X-Fleet 段的内容。在Unit段指定了这个服务启动前必须首先启动 mongo.service 服务,而在 X-Fleet 段指定了自己需要运行在与 mongo.service 相同的服务节点上。

[Unit]
Description=Employee Information Management Service
After=docker.service
After=mongo.service
[Service]
TimeoutStartSec=0
ExecStart=/opt/bin/docker-run.sh -p 3000:3000 --link mongo-ins:mongo -d --name node-app node-app node /NodeJS-Sample-App/EmployeeDB/app.js
ExecStop=/usr/bin/docker stop mongo-ins
[X-Fleet]
X-ConditionMachineOf=mongo.service

上面的两个 Unit 文件都使用到了一个 /opt/bin/docker-run.sh 脚本,用于替代 docker run 命令。这个脚本需要额外创建并放置到 /opt/bin 目录下面,其作用是检测是否已经有一个同名的容器在运行了,如果没有则执行相应的 docker run 命令,否则直接使用 docker start 命令启动已经存在的容器。其内容如下:

#!/bin/bash
PARA="${*}"
NAME=$(echo "${PARA}" | grep '\-\-name' | sed 's/.*--name \([^ ]*\).*/\1/g')
if [ "${NAME}" == "" ]; then
  echo "[ERROR] Must specify a name to the container!";
  exit -1;
fi
EXIST=$(sudo docker ps -a | grep "${NAME}[ ]*$")
if [ "${EXIST}" == "" ]; then
   sudo docker run ${PARA}
else
  sudo docker start ${NAME}
fi

二、启动服务

通过 fleetctl 命令启动服务,具体的用法在系列前面的内容里面已经介绍过了。

fleetctl start ./mongo.service
fleetctl start ./employees.service

这里为了简便直接用了 fleetctl start 命令,更推荐的启动服务方法请参考系列中关于Fleet的一篇

到这一步,这个部署在容器中的服务已经可以使用了。从外部访问服务器的 3000 端口即可打开下面这个页面,并向MongoDB服务中的数据库中添加员工信息了。

CoreOS实践指南(七):Docker容器管理服务

  • 管理容器运行状态

最后,再来看一些用于检测容器运行状态和日常管理的Docker命令。

一、查看运行日志

容器通过-d参数进入后台运行之后,其中服务输出的日志内容可以通过 docker logs 命令查看到。

$ docker logs mongo-ins
MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=d9bba1bfc8be
...

二、容器实例列表

命令 docker ps 能够列出所有当前正在运行的容器的基本信息。

$ docker ps
CONTAINER ID  IMAGE  COMMAND  CREATED   STATUS  PORTS  NAMES
d9bba1bfc8be  mongo:2  "/entrypoint.sh"  4 minutes ago  Up 4 minutes  27017/tcp  mongo-ins
22de21d77174  node:0  "/bin/bash"  3 minutes ago  Up 5 minutes  node-app
...

三、容器实例详情

使用 docker inspect 命令能够查看到指定一个容器的详细运行信息。

$ docker inspect mongo-ins
{ ... }

四、备份和还原容器

简单的提一下,用来将现有的本地镜像打包备份和还原的命令是 docker save 和 docker load。也可以直接将容器实例打包,相关命令是 docker export 和 docker import,注意 import 之后会将备份的数据恢复成一个新的本地镜像,而不是容器实例。

这两个命令的使用可以参考文档。只额外说明一个问题,既然两种还原都会将备份的内容还原为容器,为什么需要两种还原命令呢?原因在于使用 save 和 export 生成的打包效果是不太一样的,简单说就是 export 生成的备份会丢弃所有的镜像分层结构,而 save 生成的备份不会。镜像分层结构有利于减少相似镜像本地存储所需的空间,细节可参考这篇文章

以上介绍的这些命令仅仅是Docker强大功能的冰山一角,网络上已经有许多十分优秀的Docker使用教程,作为学习Docker和应用容器都是极好的途径。这里推荐一个Dockerone翻译的Docker系列文章

后话

事实上,随着CoreOS的独立容器项目 Rocket 的发起,Docker 在未来将不再是 CoreOS 和其他Linux操作系统设计容器方案的唯一选择。但作为 CoreOS 乃至整个 Linux 生态圈的应用容器服务佼佼者,Docker的王者地位还会持续很长的时间,而CoreOS始终会保持对Docker容器的一流支持(见CoreOS关于Rocket博客中的F&Q)。

正值提笔写这篇文章的那天,Bing的首页内容是泰国的曼谷港,这幅画面与Docker的Logo颇有几分神似。如此的巧合,使人不由的联想,这艘万吨货轮底下是否也正藏着一只蓄势待发的蓝鲸呢。 

CoreOS实践指南(七):Docker容器管理服务

在这一篇内容中,将重点放在了使用Docker容器管理服务的介绍,正如文章中已经指出的,例子中的有些实践(使用docker commit创建镜像,以及fleet start直接启动服务等)并不适合在实际的项目中使用。从下下篇的文章起,我们将讲解几个完整的,符合产品应用的例子。在进入正式的综合实例前,在下一篇中,会对 Systemd 和 Fleet 使用的 Unit 文件做一个更深入的探索。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这