docker 入門

http://dockone.io/article/277html

 

个人碎碎念:Docker入门指南

【编者的话】以前曾经翻译过不少Docker入门介绍的文章,之因此再翻译这篇,是由于Anders的角度很独特,思路也很调理。你也能够看下做者的演讲稿 《Docker, DevOps的将来》。本文介绍了Docker的一些基本概念、诱人的特性、Docker的工做原理、平常管理基本操做,以及一些Docker的问题的解决方案。
docker.png

什么是Docker,你应该知道些什么?

相比不少人的解释,我相信说Docker是一个轻量级的虚拟机更容易理解。另一种解释是:Docker就是操做系统中的 chroot。若是你不知道 chroot是什么的话,后一种解释可能没法帮助你理解什么是Docker。

chroot是一种操做,能改变当前运行的进程和子进程的根目录。 程序运行在这样的一个被修改的环境中,它不能访问这个环境目录树以外的文件和命令,这个被修改的环境就是“chroot牢笼”。


-- Arch Linux 的 wiki 中对 chroot 的解释

虚拟机 vs. Docker

下面这张图描述了虚拟机和Docker之间的差别。 在VM中,宿主OS上是 hypervisor(虚拟机监视器), 最上层是客户机操做系统,而Docker则使用 Docker引擎和容器。 这样解释你能理解吗? Docker引擎和hypervisor之间的区别又是什么呢?你能够列出运行在宿主OS上的进程来理解它们的区别。
vm-vs-docker.png

下面这个简单的进程树能够看出它们的差别。虽然虚拟机中运行了不少进程,可是运行虚拟机的宿主机上却只有一个进程。
# Running processes on Host for a VM
$ pstree VM

-+= /VirtualBox.app
|--= coreos-vagrant

而运行Docker引擎的主机上则能够看到全部的进程。  容器进程是运行在宿主OS上的!,他们能够经过普通的 pskill等命令进行检查和维护。
# Docker在主机中的进程
$ pstree docker

-+= /docker
|--= /bin/sh
|--= node server.js
|--= go run app
|--= ruby server.rb
...
|--= /bin/bash

全部的东西都是透明的, 意味着什么呢?意味着Docker容器比虚拟机更小,更快,更容易与其它东西集成。以下图所示。
vm-vs-docker-table.png

安装CoreOS的小型虚拟机竟然有1.2GB, 而装上busybox的小型容器只有2.5MB。最快的虚拟机启动时间也是分钟级的,而容器的启动时间一般不到一秒。在同一宿主机上安装虚拟机须要正确的设置网络, 而安装Docker很是简单。

这么来看,容器是轻量、快速而且易集成,但这并非它的所有!

Docker 是一份合约

Docker仍是开发者和运维之间的“合约”。 开发和运维在选择工具和环境时的姿态一般差异很大。开发者想要使用一些闪亮的新东西,好比Node.js、Rust、Go、微服务、Cassandra、Hadoop、blablabla.........而运维则倾向于使用以往用过的工具,由于事实证实那些旧的工具颇有效。

但这偏偏是Docker的亮点, 运维喜欢它,由于Docker让他们只要关心一件事: 部署容器, 而开发者也同样很开心,只要写好代码,而后往容器里一扔,剩下的交给运维就完事了。
devs-loves-ops.png

不过别急,这还没完。运维还能帮助开发者构建优化好的容器以便用于本地开发。

更好的资源利用

不少年前,那时候尚未虚拟化,当咱们须要建立一个新服务时,咱们必须申请实际的物理机硬件。 这可能要花上数月,依赖于公司的流程。一旦服务器到位,咱们建立好服务,不少时候它并无像咱们但愿的那样成功,由于服务器的CPU使用率只有5%。 太奢侈了。 

接着,虚拟化来了。它能够在几分钟以内把一台机器运转起来,还能够在同一硬件上运行多个虚拟机,资源使用率就不仅5%了。可是,咱们还须要给每一个服务分配一个虚拟机,所以咱们仍是不能如愿的使用这台机器。

容器化是演化进程的下一步。容器能够在几秒以内建立起来,并且还能以比虚拟机更小的粒度进行部署。

依赖

matrix-from-hell.jpg

Docker启动速度真的很酷。 可是,咱们为何不把全部的都服务部署到同一台机器上呢? 缘由很简单:依赖的问题。在同一台机器上安装多个独立的服务,无论是真是机器仍是虚拟机都是一场灾难。用Docker公司的说法是:地狱同样的矩阵依赖。

而Docker经过在容器中保留依赖关系解决了矩阵依赖的问题。

速度

roadrunner.gif

快固然不错,可是能快100倍就太难以想象了。速度让不少事情成为可能,增长了更多新的可能性。好比,如今能够快速建立新的环境,若是须要从Clojure开发环境完整的切换到Go语言吗?启动一个容器吧。须要为集成和性能测试提供生产环境DB ?启动一个容器吧! 须要从Apache切换整个生产环境到Nginx?启动容器吧!

Docker是怎么工做的?

Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上, 而后经过Socket链接从客户端访问, 客户端和守护进程也能够运行再同一主机上,但这不是必须的。Docker命令行客户端也是相似的工做方式,但它一般经过Unix域套接字而不是TCP套接字链接。

守护进程从客户端接受命令并管理运行在主机上的容器。
client-server.png

Docker 概念及相互做用

  • 主机, 运行容器的机器。
  • 镜像,文件的层次结构,以及包含如何运行容器的元数据
  • 容器,一个从镜像中启动,包含正在运行的程序的进程
  • Registry, 镜像仓库
  • 卷,容器外的存储
  • Dockerfile, 用于建立镜像的脚本
    docker-interactions.png

    咱们能够经过Dockerfile来构建镜像, 还能够经过commit一个运行的容器来建立一个镜像,这个镜像能够会被标记,能够推到Registry或者从Registry上拉下来,能够经过建立或者运行镜像的方式来启动容器,能够被stop,也能够经过rm来移除它。

    镜像

    镜像是一种文件结构,包含如何运行容器的元数据。Dockerfile中的每条命令都会在文件系统中建立一个新的层次结构,文件系统在这些层次上构建起来,镜像就构建于这些联合的文件系统之上。
    docker-image.png

    当容器启动后,全部镜像都会统一合并到一个进程中。 联合文件系统中的文件被删除时, 它们只是被标记为已删除,但实际上仍然存在。
    # Commands for interacting with images
    $ docker images  # 查看全部镜像.
    $ docker import  # 从tarball建立镜像
    $ docker build   # 经过Dockerfile建立镜像
    $ docker commit  # 从容器中建立镜像
    $ docker rmi     # 删除镜像
    $ docker history # 列出镜像的变动历史
    

    镜像大小

    这是一些常用的镜像相关的数据:
  • scratch - 基础镜像, 0个文件,大小为0
  • busybox - 最小Unix系统,2.5MB,10000个文件
  • debian:jessie - Debian最新版, 122MB, 18000 个文件
  • ubuntu:14.04 - 188MB,23000 个文件

建立镜像

能够经过 docker commit container-iddocker import url-to-tar或者 docker build -f Dockerfile .来建立镜像。
先看commit的方式:
# 经过commit的方式来建立镜像
$ docker run -i -t debian:jessie bash
root@e6c7d21960:/# apt-get update
root@e6c7d21960:/# apt-get install postgresql
root@e6c7d21960:/# apt-get install node
root@e6c7d21960:/# node --version
root@e6c7d21960:/# curl https://iojs.org/dist/v1.2.0/iojs-v1.2.0-
linux-x64.tar.gz -o iojs.tgz
root@e6c7d21960:/# tar xzf iojs.tgz
root@e6c7d21960:/# ls
root@e6c7d21960:/# cd iojs-v1.2.0-linux-x64/
root@e6c7d21960:/# ls
root@e6c7d21960:/# cp -r * /usr/local/
root@e6c7d21960:/# iojs --version
1.2.0
root@e6c7d21960:/# exit
$ docker ps -l -q
e6c7d21960
$ docker commit e6c7d21960 postgres-iojs
daeb0b76283eac2e0c7f7504bdde2d49c721a1b03a50f750ea9982464cfccb1e

从上面能够看出,咱们能够经过 docker commit来建立镜像,可是这种方式有点凌乱并且很难复制, 更好的方式是经过Dockerfile来构建镜像,由于它步骤清晰而且容易复制:
FROM debian:jessie
# Dockerfile for postgres-iojs

RUN apt-get update
RUN apt-get install postgresql
RUN curl https://iojs.org/dist/iojs-v1.2.0.tgz -o iojs.tgz
RUN tar xzf iojs.tgz
RUN cp -r iojs-v1.2.0-linux-x64/* /usr/local

而后用下面的命令来构建:
$ docker build -tag postgres-iojs .

Dockerfile中的每个命令都建立了新版的layer,一般把相似的命令放在一块儿,经过&&和续行符号把命令组合起来:
FROM debian:jessie
# Dockerfile for postgres-iojs

RUN apt-get update && \
  apt-get install postgresql && \
  curl https://iojs.org/dist/iojs-v1.2.0.tgz -o iojs.tgz && \
  tar xzf iojs.tgz && \
  cp -r iojs-v1.2.0-linux-x64/* /usr/local

这些行中命令的顺序很重要,由于Docker为了加速镜像的构建,会缓存中间的镜像。 组织Dockerfile的顺序时,注意把常常变化的行放在文件的底部,当缓存中相关的文件改变时,镜像会从新运行,即便Dockerfile中的行没有发生变化也是如此。

Dockerfile 中的命令

Dockerfile 支持13个命令, 其中一些命令用于构建镜像,另一些用于从镜像中运行容器,这是一个关于命令何时被用到的表格:
dockerfile-commands.png

BUILD 命令:

  • FROM - 新镜像是基于哪一个镜像的
  • MAINTAINER - 镜像维护者的姓名和邮箱地址
  • COPY - 拷贝文件和目录到镜像中
  • ADD - 同COPY同样,但会自动处理URL和解压tarball压缩包
  • RUN - 在容器中运行一个命令, 好比:apt-get install
  • ONBUILD - 当构建一个被继承的Dockerfile时运行命令
  • .dockerignore - 不是一个命令, 但它能控制什么文件被加入到构建的上下文中,构建镜像时应该包含.git以及其它的不须要的文件。

RUN 命令:

  • CMD - 运行容器时的默认命令,能够被命令行参数覆盖
  • ENV - 设置容器内的环境变量
  • EXPOSE - 从容器中暴露出端口, 必须显式的经过在主机上的RUN命令带上-p或者-P来暴露端口
  • VOLUME - 指定一个在文件系统以后的存储目录。若是不是经过docker run -v设置的, 那么将被建立为/var/lib/docker/volumes
  • ENTRYPOINT - 指定一个命令不会被docker run image cmd命令覆盖。经常使用于提供一个默认的可执行程序并使用命令做为参数。

BUILD, RUN命令都有的命令:

  • USER - 为RUN、CMD、ENTRYPOINT命令设置用户
  • WORKDIR - 为RUN、CMD、ENTRYPOINT、ADD、COPY命令设置工做目录
    docker-image.png

运行的容器

容器启动后,进程在它能够运行的联合文件系统中得到了新的可写层。

从1.5版本起,它还可让最顶层的layer设置为只读,强制咱们为全部文件输出(如日志、临时文件)使用卷。
# 用于与容器交互的命令
$ docker create  # 建立一个容器,但不启动它
$ docker run     #  建立并启动一个容器
$ docker stop    # 中止容器
$ docker start   #  启动容器
$ docker restart # 重启容器
$ docker rm      # 删除容器
$ docker kill    #  给容器发送kill信号
$ docker attach  # 链接到正在运行的容器中
$ docker wait    # 阻塞直到容器中止为止
$ docker exec    # 在运行的容器中执行一条命令

docker run

如上所述,  docker run是用户启动新容器的命令, 这里是一些通用的运行容器的方法:
container.png

# 交互式运行容器
$ docker run -it --rm ubuntu

这是一个可让你像普通的终端程序同样交互式的运行容器的方法, 若是你想把管道输出到容器中,可使用-t选项。
  • --interactive (-i) - 将标准输入发送给进程
  • -tty (-t) - 告诉进程有终端链接。 这个功能会影响程序的输出和它如何处理Ctrx-C等信号。
  • --rm - 退出时删除镜像。

    # 后台运行容器
    $ docker run -d hadoop
    

    docker run -env

    # 运行一个命名容器并给它传一些环境变量
    $ docker run \
    --name mydb \
    --env MYSQL_USER=db-user \
    -e MYSQL_PASSWORD=secret \
    --env-file ./mysql.env \
    mysql
    
  • --name - 给容器命名, 不然它是一个随机容器
  • --env (-e)- 设置容器中的环境变量
  • --env-file - 从env-file中引入全部环境变量(同Linux下的source env-file 功能)
  • mysql - 指定镜像名为 mysql:lastest

docker run -publish

# 发布容器的80端口到主机上的随机端口
$ docker run -p 80 nginx

# 发布容器端口80和主机上的8080端口
$ docker run -p 8080:80 nginx

# 发布容器80端口到主机127.0.0.0.1的8080端口
$ docker run -p 127.0.0.1:8080:80 nginx

# 发布全部容器中暴露的端口到主机的随机端口上
$ docker run -P nginx

nginx 镜像,好比暴露出80和443端口。
FROM debian:wheezy
  MAINTAINER NGINX "docker-maint@nginx.com"

 EXPOSE 80 443

docker run --link

# 启动postgres容器,给它起名为mydb
$ docker run --name mydb postgres

# 把mydb 连接到 myqpp 的db
$ docker run --link mydb:db myapp

链接容器须要设置容器到被链接的容器之间的网络,有两件事要作:
  • 经过容器的链接名,更新 /etc/hosts 。 在上面的例子中,链接名是db, 能够方便的经过名字db来访问容器。
  • 为暴露的端口设置环境变量。这个好像没啥实际用处,你也能够经过 主机名:端口的形式访问对应的端口。

docker run limits

还能够经过run limits来限制容器可使用的主机资源
# 限制内存大小
$ docker run -m 256m yourapp

# 限制进程可使用的cpu份数(cpu shares)(总CPU份数为1024)
$ docker run --cpu-shares 512 mypp

# 改变运行进程的用户为www,而不是root(出于安全考虑)
$ docker run -u=www nginx

设置CPU份数为1024中的512份并不意味着可使用一半的CPU资源,这意味着在一个无任何限制的容器中,它最多可使用一半的份数。好比咱们有两个有1024份的容器,和一个512份的容器(1024:1024:512) ,那么512份的那个容器,就只能获得1/5的总CPU份数

docker exec container

docker exec 容许咱们在已经运行的容器内部执行命令,这点在debug的时候颇有用。
# 使用id 6f2c42c0在容器内部运行shell
$ docker exec -it 6f2c42c0 sh

volumes.png

卷提供容器外的持久存储。 这意味着若是你提交了新的镜像,数据将不会被保存。
# Start a new nginx container with /var/log as a volume
$ docker run  -v /var/log nginx

若是目录不存在,则会被自动建立为:/var/lib/docker/valumes/ec3c543bc..535

实际的目录名能够经过命令: docker inspect container-id 找到。
# 启动新的nginx容器,设置/var/log为卷,并映射到主机的/tmp目录下
$ docker run -v /tmp:/var/log nginx

还可使用 --valumes-from选项从别的容器中挂载卷。
# 启动容器db
$ docker run -v /var/lib/postgresql/data --name mydb postgres

# 启动backup容器,从mydb容器中挂载卷
$ docker run --volumes-from mydb backup

Docker Registry

Docker Hub是Docker的官方镜像仓库,支持私有库和共有库,仓库能够被标记为 官方仓库,意味着它由该项目的维护者(或跟它有关的人)策划。 

Docker Hub 还支持自动化构建来自Github和Bitbucket的项目,若是启用自动构建功能,那么每次你提交代码到代码库都会自动构建镜像。

即便你不想用自动构建,你仍是能够直接 docker push到Docker Hub,Docker pull则会拉取镜像下来。 docker run 一个本地不存在的镜像,则会自动开始 docker pull操做。 

你也能够在任意地方托管镜像,官方有 Registry的开源项目,可是,还有不少Bug。

此外,Quay、Tutum和Google 还提供私有镜像托管服务。

检查容器

检查容器的命令有一大把:
$ docker ps      # 显示运行的容器
$ docker inspect # 显示容器信息(包括ip地址)
$ docker logs    # 获取容器中的日志
$ docker events  # 获取容器事件
$ docker port    # 显示容器的公开端口
$ docker top     # 显示容器中运行的进程
$ docker diff    # 查看容器文件系统中改变的文件
$ docker stats   # 查看各类纬度数据、内存、CPU、文件系统等

下面详细讲一下 docker ps 和 docker inspect,这两个命令最经常使用了。
# 列出全部容器,包括已中止的。
$ docker ps --all
CONTAINER ID   IMAGE            COMMAND    NAMES
9923ad197b65   busybox:latest   "sh"       romantic_fermat
fe7f682cf546   debian:jessie    "bash"     silly_bartik
09c707e2ec07   scratch:latest   "ls"       suspicious_perlman
b15c5c553202   mongo:2.6.7      "/entrypo  some-mongo
fbe1f24d7df8   busybox:latest   "true"     db_data


# Inspect the container named silly_bartik
# Output is shortened for brevity.
$ docker inspect silly_bartik
    1 [{
    2     "Args": [
    3         "-c",
    4         "/usr/local/bin/confd-watch.sh"
    5     ],
    6     "Config": {
   10         "Hostname": "3c012df7bab9",
   11         "Image": "andersjanmyr/nginx-confd:development",
   12     },
   13     "Id": "3c012df7bab977a194199f1",
   14     "Image": "d3bd1f07cae1bd624e2e",
   15     "NetworkSettings": {
       16         "IPAddress": "",
   18         "Ports": null
   19     },
   20     "Volumes": {},
   22 }]

技巧花招

获取容器id。写脚本时颇有用。
# Get the id (-q) of the last (-l) run container
# 获取最后(-l)一个启动的容器id(-q)
$ docker ps -l -q
c8044ab1a3d0

docker inspect能够带格式化的字符串----Go语言模板做为参数,详细描述所需的数据。写脚本时同时有用。
$ docker inspect -f '{{ .NetworkSettings.IPAddress }}' 6f2c42c05500
172.17.0.11

使用 docker exec来跟运行中的容器进行交互。
# 获取容器环境变量
$ docker exec -it 6f2c42c05500 env

PATH=/usr/local/sbin:/usr...
HOSTNAME=6f2c42c05500
REDIS_1_PORT=tcp://172.17.0.9:6379
REDIS_1_PORT_6379_TCP=tcp://172.17.0.9:6379
...

经过卷来避免每次运行时都重建镜像, 下面是一个Dockerfile,每次构建时,会拷贝当前目录到容器中。
1 FROM dockerfile/nodejs:latest
  2
  3 MAINTAINER Anders Janmyr "anders@janmyr.com"
  4 RUN apt-get update && \
  5   apt-get install zlib1g-dev && \
  6   npm install -g pm2 && \
  7   mkdir -p /srv/app
  8
  9 WORKDIR /srv/app
 10 COPY . /srv/app
 11
 12 CMD pm2 start app.js -x -i 1 && pm2 logs
 13

构建并运行镜像:
$ docker build -t myapp .
$ docker run -it --rm myapp

为避免重建,建立一次性镜像并在运行时挂载本地目录。

安全

security.jpg

你们可能据说过使用Docker不那么安全。这不是假话,但这不成问题。 

目前Docker存在如下安全问题:
  • 镜像签名未被正确的核准。
  • 若是你在容器中拥有root权限,那你潜在的拥有对真个主机的root权限。

安全解决办法:
  • 从你的私有仓库中使用受信任的镜像
  • 尽可能不要以root运行容器
  • 把容器中的root看成是主机上的root? 仍是把容器的根目录设置为容器内的根目录 ?

若是服务器上全部的容器都是你的,那你不须要担忧他们之间会有危险的交互。

“选择”容器

我给选择两字加了引号, 由于目前根本没有任何别的选择, 可是不少容器爱好者想玩玩,好比Ubuntu的LXD、微软的Drawbridge,还有 Rocket

Rocket由CoreOS开发,CoreOS是一个很大的容器平台。 他们开发Rocket的理由是以为Docker公司让Docker变得臃肿,而且还和CoreOS有业务冲突。

他们在这个新的容器中,尝试移除那些由于历史缘由而留下来的Docker瑕疵。并经过 socket activation提供简单的容器和完全的安全构建。
container-options.png

编排

当咱们把应用程序拆开到多个不一样的容器中时,会产生一些新的问题。怎么让不一样的部分进行通讯呢? 这些容器在单个主机上怎么办? 多个主机上又是怎么处理? 

单个主机上,Docker经过链接来解决编排的问题。 

为简化容器的连接操做,Docker提供了一个叫 docker-compose的工具。(之前它叫 fig, 由另外一家公司开发,而后最近Docker收购了他们)

docker-compose

fig.png

docker-compose在单个 docker-compose.yml文件中声明多个容器的信息。来看一个例子,管理web和redis两个容器的配置文件:
1 web:
  2   build: .
  3   command: python app.py
  4   ports:
  5    - "5000:5000"
  6   volumes:
  7    - .:/code
  8   links:
  9    - redis
 10 redis:
 11   image: redis

启动上述容器,可使用 docker-compose up命令
$ docker-compose up
  Pulling image orchardup/redis...
  Building web...
  Starting figtest_redis_1...
  Starting figtest_web_1...
  redis_1 | [8] 02 Jan 18:43:35.576 # Server
  started, Redis version 2.8.3
  web_1   |  * Running on http://0.0.0.0:5000/

也能够经过detached模式(detached mode)启动:  docker-compose up -d,而后能够经过 docker-compose ps查看容器中跑了啥东西:
$ docker-compose up -d
Starting figtest_redis_1...
Starting figtest_web_1...
$ docker-compose ps
Name              Command                    State   Ports
------------------------------------------------------------
figtest_redis_1   /usr/local/bin/run         Up
figtest_web_1     /bin/sh -c python app.py   Up      5000->5000

还能够同时让命令在一个容器或者多个容器中同时工做。
# 从web容器中获取环境变量
$ docker-compose run web env

# 扩展到多个容器中(Scale to multiple containers)
$ docker-compose scale web=3 redis=2

# 从全部容器中返回日志信息
$ docker-compose logs

从以上命令能够看出,扩展很容易,不过应用程序必须写成支持处理多个容器的方式。在容器外,不支持负载均衡。

Docker托管

不少公司想作在云中托管Docker的生意,以下图。
docker-hosting-providers.png

这些提供商尝试解决不一样的问题, 从简单的托管到作"云操做系统"。其中有两家比较有前景:

CoreOS

如上图所示,CoreOS是能够在CoreOS集群中托管多个容器的一系列服务的集合:
core-os.png

  • CoreOS Linux发行版是裁剪的Linux,在初次启动时使用114MB的RAM,没有包管理器, 使用Docker或者它本身的Rocket运行一切程序。
  • CoreOS 使用Docker(或Rocket)在主机上安装应用。
  • 使用systemd做为init服务,它的性能超级好,还能很好的处理启动依赖关系, 强大的日志系统,还支持socket-activation。
  • etcd 是分布式的,一致性 K-V 存储用于配置共享和服务发现。
  • fleet,集群管理器,是systemd的扩展,能与多台机器工做,采用etcd来管理配置并运行在每个台CoreOS服务器上。

AWS

Docker容器托管在Amazon有两种途径:
  • Elastic Beanstalk部署Docker容器,它工做的很好,但就是太慢了, 一次全新的部署须要好几分钟,感受跟通常的容器秒级启动不大对劲。
  • ECS、Elastic Container Server是Amazon上游容器集群解决方案, 目前还在预览版3,看起来颇有前途,跟Amazon其它服务同样,经过简单的web service调用与它交互。

总结

  • Docker is here to stay
  • 解决了依赖问题
  • 容器各方面都很快
  • 有集群解决方案,但不能无缝对接
相关文章
相关标签/搜索