最近的项目中用到了Docker,感觉超级好用。写下这篇文章作为自己学习的一个小结,也作为一篇Docker的入门介绍。
本文由以下内容组成:
- 什么是Docker
- Docker基本概念
- 容器和传统VM的区别
- 安装Docker
- Docker命令简介
- 创建Docker镜像
- 多容器部署
什么是Docker
Docker用Go语言开发实现,基于Linux内核的cgroup,namespace,和AUFS类的Union FS等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。
Docker基本概念
镜像(image)- 一个独立的文件系统,类似虚拟机里的镜像,包含运行时需要的系统、软件、代码、库、环境变量、配置文件等
容器(container)- 由镜像(image)创建的运行实例,类似虚拟机,可以对它执行启动、停止、删除等操作
仓库(repository)- 提供集中存储、镜像分发的服务,类似github。用户可以从仓库(repository)上传或下载镜像
容器和传统VM的区别
传统VM架构
容器架构
每个虚拟机都有自己独立的操作系统,而不同的容器可以共享同一个操作系统。虚拟机面向操作系统,而Docker是面向应用的。容器一般被设计为运行一个主要进程,而不是管理多个进程集合。
安装Docker
以ubuntu为例,运行以下命令
$ apt-get install docker
$ apt-get install docker.io
测试docker是否安装成功,运行
$ docker run hello-world
Hello from Docker.
This message shows that your installation appears to be working correctly.
...
Docker命令简介
以busybox镜像为例
下载镜像
$ docker pull busybox
这条命令从docker hub上下载busybox镜像存在本地
列出本地的镜像
$ docker images
REPOSITORY
busybox latest 6ad733544a63 3 weeks ago 1.129 MB
基于镜像创建容器
$ docker run busybox
$
这里没有任何输出,容器被创建后并没有运行任何命令,所以创建后就退出了
在容器中执行命令
$ docker run busybox echo "hello from busybox"
hello from busybox
echo命令退出,容器也随即退出。
显示所有的容器
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0f6621b18dbe busybox "sh" 3 minutes ago Exited (0) 3 minutes ago desperate_torvalds
显示正在运行的容器
$ docker run -d busybox top # 启动一个容器,容器中运行top命令,这里-d表示detach模式
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
27c2844e3a5d busybox "top" 5 minutes ago Up 5 minutes sleepy_wilson
在容器中运行命令
$ docker run -it busybox # -it表示连接到容器中的tty
/ # ls
bin dev etc home proc root sys tmp usr var
/ # echo "hello"
hello
删除容器
$ docker rm 0f6621b18dbe
0f6621b18dbe
删除镜像
$ docker rmi busybox
Untagged: busybox:latest
Untagged: busybox@sha256:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0
Deleted: sha256:6ad733544a6317992a6fac4eb19fe1df577d4dec7529efec28a5bd0edad0fd30
Deleted: sha256:0271b8eebde3fa9a6126b1f2335e170f902731ab4942f9f1914e77016540c7bb
在Docker Hub上搜索镜像
$ docker search busybox # 搜索image名字包含busybox的镜像
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
busybox Busybox base image. 1149 [OK]
progrium/busybox 66 [OK]
hypriot/rpi-busybox-httpd Raspberry Pi compatible Docker Image with ... 39
radial/busyboxplus Full-chain, Internet enabled, busybox made... 16 [OK]
hypriot/armhf-busybox Busybox base image for ARM. 8
armhf/busybox Busybox base image. 4
arm32v7/busybox Busybox base image. 3
...
检查容器中的命令输出
$ docker run -d busybox top # 启动一个容器
$ docker logs 10b72de4bd77 # 查看容器中top的输出
Mem: 8700192K used, 15989388K free, 247764K shrd, 299432K buff, 6261884K cached
CPU: 0.0% usr 0.1% sys 0.0% nic 99.7% idle 0.0% io 0.0% irq 0.0% sirq
Load average: 0.16 0.09 0.11 1/363 6
创建Docker镜像
Dockerfile可用来自动化Docker镜像的创建,它包含一系列指令来描述如何创建一个镜像。
这里我们来展示如何用Dockerfile创建一个zookeeper的镜像。
首先需要在Dockerfile中指定base镜像,FROM关键字用于指定base镜像。因为zookeeper要用到java,我们的镜像使用openjdk作为base
FROM openjdk
MAINTAINER关键字描述镜像的创建者
MAINTAINER Zhongqiang Shen
WORKDIR设置容器内的当前工作目录,如果不存在则创建目录
WORKDIR /tmp
启动zookeeper服务需要从官网下载zookeeper包,撰写conf/zoo.cfg,并启动zookeeper进程。ADD关键字将URL中的内容下载到指定目录中
ADD http://apache.osuosl.org/zookeeper/stable/zookeeper-3.4.10.tar.gz /tmp
RUN关键字可以在容器中运行命令。在容器中解压zookeeper包,并将加压后的包移到/opt/zookeeper位置
RUN tar -xzf zookeeper-3.4.10.tar.gz
RUN mv zookeeper-3.4.10 /opt/zookeeper
设置zookeeper的路径为当前的工作目录
WORKDIR /opt/zookeeper
撰写conf/zoo.cfg
RUN echo "tickTime=2000" >> conf/zoo.cfg
RUN echo "dataDir=/var/lib/zookeeper" >> conf/zoo.cfg
RUN echo "clientPort=2181" >> conf/zoo.cfg
暴露容器的2181端口,使用expose关键字
EXPOSE 2181
启动zookeeper进程,使用CMD关键字。start-foreground参数让zookeeper在前台运行,如果没有这个参数,.sh脚本退出后会导致容器也退出
CMD ["/opt/zookeeper/bin/zkServer.sh", "start-foreground"]
这里需要注意RUN和CMD的区别,RUN用于创建镜像的时候执行命令,每次执行命令都会创建新的镜像层。CMD用于指定容器启动后默认执行的命令和参数。
完整的Dockerfile是这样的:
FROM openjdk
MAINTAINER Zhongqiang Shen
WORKDIR /tmp
ADD http://apache.osuosl.org/zookeeper/stable/zookeeper-3.4.10.tar.gz /tmp
RUN tar -xzf zookeeper-3.4.10.tar.gz
RUN mv zookeeper-3.4.10 /opt/zookeeper
WORKDIR /opt/zookeeper
RUN echo "tickTime=2000" >> conf/zoo.cfg
RUN echo "dataDir=/var/lib/zookeeper" >> conf/zoo.cfg
RUN echo "clientPort=2181" >> conf/zoo.cfg
EXPOSE 2181
CMD ["/opt/zookeeper/bin/zkServer.sh", "start-foreground"]
创建完Dockerfile,就可以用下面的命令来创建镜像了
$ docker build -t zookeeper .
Sending build context to Docker daemon 5.632kB
Step 1/12 : FROM openjdk
latest: Pulling from library/openjdk
3e17c6eae66c: Pull complete
fdfb54153de7: Pull complete
a4ca6e73242a: Pull complete
93bd198d0a5f: Pull complete
ca4d78fb08d6: Pull complete
ad3d1bdcab4b: Pull complete
4853d1e6d0c1: Pull complete
49e4624ad45f: Pull complete
bcbcd4c3ef93: Pull complete
Digest: sha256:b89826260c9f5ebb94ebff7ef23720f2b6de9f879df52e91afd112f53f5f7531
Status: Downloaded newer image for openjdk:latest
---> 377371113dab
Step 2/12 : MAINTAINER zhongqiang Shen
---> Running in 03bf1c8ef563
---> 7cd7ffa57b0c
Removing intermediate container 03bf1c8ef563
Step 3/12 : WORKDIR /tmp
---> b180924d0413
Removing intermediate container 1461e2b93f70
Step 4/12 : ADD http://apache.osuosl.org/zookeeper/stable/zookeeper-3.4.10.tar.gz /tmp
Downloading [==================================================>] 35.04MB/35.04MB
---> c2858e418073
Step 5/12 : RUN tar -xzf zookeeper-3.4.10.tar.gz
---> Running in 0cb7b253c12f
---> 0f7afb29ae74
Removing intermediate container 0cb7b253c12f
Step 6/12 : RUN mv zookeeper-3.4.10 /opt/zookeeper
---> Running in 68ce7228ca7e
---> 65d309c5340a
Removing intermediate container 68ce7228ca7e
Step 7/12 : WORKDIR /opt/zookeeper
---> b2dbec2aed3c
Removing intermediate container 8ac9df07f732
Step 8/12 : RUN echo "tickTime=2000" >> conf/zoo.cfg
---> Running in ef1d9dd5269a
---> 0c20dd205282
Removing intermediate container ef1d9dd5269a
Step 9/12 : RUN echo "dataDir=/var/lib/zookeeper" >> conf/zoo.cfg
---> Running in 7dcdb7eb07b1
---> a0a0a7341dba
Removing intermediate container 7dcdb7eb07b1
Step 10/12 : RUN echo "clientPort=2181" >> conf/zoo.cfg
---> Running in c2b0127e5cca
---> 6f7564eeaf4f
Removing intermediate container c2b0127e5cca
Step 11/12 : EXPOSE 2181
---> Running in cd97242108e5
---> eb91473e8a4c
Removing intermediate container cd97242108e5
Step 12/12 : CMD /opt/zookeeper/bin/zkServer.sh start-foreground
---> Running in 665686b5ec56
---> c4515a39ff83
Removing intermediate container 665686b5ec56
Successfully built c4515a39ff83
Successfully tagged zk:latest
这样一个docker镜像就创建好了。可以用下面的命令来启动它
$ docker run zookeeper
多容器部署
一个应用通常由多个服务构成,将这些服务运行在容器中,就涉及到多个容器的部署。使用Docker Compose可以实现复杂的多容器应用的部署运行。
Docker Compose使用docker-compose.yml来定义服务。在docker-compose.yml中,所有的容器通过services来定义。
这里以kafka为例,kafka下层使用zookeeper作协调,因此这里需要定义zookeeper和kafka两个服务,先启动zookeeper,后启动kafka。
首先安装Docker Compose
$ apt-get install docker-compose
定义docker-compose.yml,如下:
version: '2'
services:
zookeeper:
container_name: iop-zookeeper
image: jplock/zookeeper
ports:
- "2181:2181"
kafka:
container_name: iop-kafka
image: wurstmeister/kafka
environment:
KAFKA_ZOOKEEPER_CONNECT: iop-zookeeper:2181
KAFKA_CREATE_TOPICS: "metrics"
KAFKA_ADVERTISED_HOST_NAME: localhost
KAFKA_BROKER_ID: 1
ports:
- "9092:9092"
links:
- zookeeper
version指定Docker Compose的版本
container_name指定容器的名字
image指定使用的镜像的名字,这里使用了docker hub上现有的Dockerfile来创建zookeeper和kafka的镜像
ports定义端口映射
environment设置环境变量
links定义容器之间的关联关系和依赖关系,这里kafka依赖于zookeeper,定义了这个依赖关系后,kafka启动前会先启动zookeeper
定义了docker-compose.yml文件后,就可以通过如下命令来一键启动服务
$ docker-compose up -d # -d表示后台模式运行服务
Pulling zookeeper (jplock/zookeeper:latest)...
latest: Pulling from jplock/zookeeper
b56ae66c2937: Pull complete
81cebc5bcaf8: Pull complete
3b27fd892ecb: Pull complete
40bb2918284a: Pull complete
Digest: sha256:5fe911a016393439a963bcab2f1cc03d107816ce2c6977bfa77bfb45edef5ad0
Status: Downloaded newer image for jplock/zookeeper:latest
Pulling kafka (wurstmeister/kafka:latest)...
latest: Pulling from wurstmeister/kafka
90f4dba627d6: Pull complete
11dbde1d93a0: Pull complete
c89218b0f06c: Pull complete
134279c08227: Pull complete
341b4d59b9c3: Pull complete
2ce0b628d981: Pull complete
82b065c991b8: Pull complete
d4f3b865c0e2: Pull complete
af829f3a4ec8: Pull complete
Digest: sha256:2aa183fd201d693e24d4d5d483b081fc2c62c198a7acb8484838328c83542c96
Status: Downloaded newer image for wurstmeister/kafka:latest
Creating iop-zookeeper ...
Creating iop-zookeeper ... done
Creating iop-kafka ...
Creating iop-kafka ... done
运行下列命令可以看到容器已启动
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
08a14f1c462a wurstmeister/kafka "start-kafka.sh" 28 seconds ago Up 26 seconds 0.0.0.0:9092->9092/tcp iop-kafka
f47d27f80aac jplock/zookeeper "/opt/zookeeper/bi..." 28 seconds ago Up 27 seconds (health: starting) 2888/tcp, 0.0.0.0:2181->2181/tcp, 3888/tcp iop-zookeeper
我们用python写个程序来测试一下启动的kafka服务
from optparse import OptionParser
from kafka import KafkaConsumer
from kafka import KafkaProducer
parser = OptionParser()
parser.add_option("--action", action="store", choices=["produce", "consume"], type="choice", dest="action")
(options, args) = parser.parse_args()
if options.action == "produce":
producer = KafkaProducer(bootstrap_servers="localhost:9092")
for i in range(10):
producer.send("test", str(i))
producer.flush()
if options.action == "consume":
consumer = KafkaConsumer("test", bootstrap_servers=["localhost:9092"], auto_offset_reset='earliest')
for message in consumer:
print message
上面的代码包含两个功能:向kafka队列生产数据和从kafka队列消费数据。
我们先向kafka队列生产数据
$ python test.py --action=produce
随后从kafka队列中消费数据,并打印出数据
$ python test.py --action=consume
ConsumerRecord(topic=u'test', partition=0, offset=0, timestamp=1511934733036, timestamp_type=0, key=None, value='0', checksum=1395146535, serialized_key_size=-1, serialized_value_size=1)
ConsumerRecord(topic=u'test', partition=0, offset=1, timestamp=1511934733045, timestamp_type=0, key=None, value='1', checksum=-7035501, serialized_key_size=-1, serialized_value_size=1)
ConsumerRecord(topic=u'test', partition=0, offset=2, timestamp=1511934733047, timestamp_type=0, key=None, value='2', checksum=1650992148, serialized_key_size=-1, serialized_value_size=1)
ConsumerRecord(topic=u'test', partition=0, offset=3, timestamp=1511934733049, timestamp_type=0, key=None, value='3', checksum=195437617, serialized_key_size=-1, serialized_value_size=1)
ConsumerRecord(topic=u'test', partition=0, offset=4, timestamp=1511934733051, timestamp_type=0, key=None, value='4', checksum=-1858641489, serialized_key_size=-1, serialized_value_size=1)
ConsumerRecord(topic=u'test', partition=0, offset=5, timestamp=1511934733053, timestamp_type=0, key=None, value='5', checksum=-349298306, serialized_key_size=-1, serialized_value_size=1)
ConsumerRecord(topic=u'test', partition=0, offset=6, timestamp=1511934733055, timestamp_type=0, key=None, value='6', checksum=1993515257, serialized_key_size=-1, serialized_value_size=1)
ConsumerRecord(topic=u'test', partition=0, offset=7, timestamp=1511934733057, timestamp_type=0, key=None, value='7', checksum=-824249467, serialized_key_size=-1, serialized_value_size=1)
ConsumerRecord(topic=u'test', partition=0, offset=8, timestamp=1511934733059, timestamp_type=0, key=None, value='8', checksum=1519664681, serialized_key_size=-1, serialized_value_size=1)
ConsumerRecord(topic=u'test', partition=0, offset=9, timestamp=1511934733061, timestamp_type=0, key=None, value='9', checksum=546143992, serialized_key_size=-1, serialized_value_size=1)
可以看到之前插入的数据被成功的读取到。
总结
本文对Docker做了个简单介绍,包括Docker的基本概念、基本命令、如何创建Docker镜像、以及如何部署多容器。
除以上内容,Kubernetes也是Docker生态圈中的重要一员。Kubernetes是一个开源的容器集群管理系统,提供资源调度、均衡容灾、服务注册、动态扩缩容等功能,可以作为下一步学习的内容。