docker 简明教程

本教程前一段的vagrant 是对于windows 或mac 平台的,因为docker 是基于linux的,如果没有linux系统环境,推荐使用vagrant来建立一个虚拟机,官方提供一个可以直接从操作docker的linux镜像,我觉得docker 和vagrant 有很多像的地方,可以多了解docker,有linux环境的直接看docker部分即可。像了解vagrant的同学,移步-》》vagrant中文文档

已有linux环境跳过vagrant,直接进去docker部分-》》go

Docker已经迅速发展了一段时间,不少同学可能都在对Docker的应用,实际的应用,解决开发和运营中的实际问题,如何应用Docker,对docker能做什么有了更多的需求,为此,我整理了一份 Docker应用教程 ,致力提供更多的 Docker应用案例和问题解决。初学的同学还请有先按本文了解docker的基本操作。

Docker 是个容器,像个碗或者盆,其实更像个橱柜 带格子的那种.

你可以把喜欢的东西放在各种各种的格子里,然后不管橱柜搬到哪,就你在里面放置的好玩意不会变。

这样你就可以轻松的在ubuntu上运行一个centos

下面是老外做的一个不是很直观也能看明白的直观图,对比docker和VM的不同:

 

这里有镜像可以下载 these slides 都是Docker提供的(不足最近github的访问速度是真xxx,随意最好找找有没有代理加快下载,如果对docker不了解,还是要谨慎一些,二次发布的镜像,也许会有问题).

获取docker

docker对mac支持不是很好,可以用boot2docker来解决.老外推荐使用CoreOs来操作Vagrant(这又是一个神器,将来会陆续在我的网站出现,请持续关注)

 

老外在这里强烈推荐CoreOS,说怎么怎么好,我还是推荐先使用ubuntu或者在windows上装个vbox在用ubuntu,coreos作为一个新型的服务器级别的系统,也十分值得关注,因为CoreOs将作为主力的docker容器发展。

下面这一段是老外将他在CoreOS安装docker的事,主要是 Vagrantfile (这是一个Vagrant可以识别的配置文件):

config.vm.box ="coreos"
config.vm.box_url ="http://storage.core-os.net/coreos/amd64-generic/dev-channel/coreos_production_vagrant.box"
config.vm.network "private_network",
ip:"172.12.8.150"

如果使用NFS 就加下面一段:

# This will require sudo access when using "vagrant up"
config.vm.synced_folder ".","/home/core/share",
id:"core",:nfs =>true,:mount_options =>['nolock,vers=3,udp']

如果使用vmware不使用virtualbox:

config.vm.provider :vmware_fusion do|vb,override|override.vm.box_url ="http://storage.core-os.net/coreos/amd64-generic/dev-channel/coreos_production_vagrant_vmware_fusion.box"end

最好要加上一个插件:

# plugin conflictifVagrant.has_plugin?("vagrant-vbguest")then
config.vbguest.auto_update =falseend

如果你不使用CoreOS而是使用其他的,可以在这个页面 this page 找到安装方式(这个是官方的,这里是民间的http://www.simapple.com/255.html,陆续完善中). Note: Docker是基于 Ubuntu开发的随意选择什么,你懂得 .

如果在CoreOS上操作过程中,提示重启,请不要担心,这是为了启用特性所做的操作

下面为docker正式内容

你的第一个容器

第一步尝试是必须的,虽然你可能不是直接发现docker的强大,但是足以“管中窥豹”(这老外比我都罗嗦)

docker有一些基础的容器,也接受民间提供制作好的容器,以后你如果想贡献,也可以在这里 index.docker.io 发布自己的容器.

最流行最基础的Docker镜像还是“Ubuntu”

如果没有这个镜像,那么需要下载~

Run Bash:

docker run ubuntu /bin/bash

什么事情都没有发生! 哈哈, 实际上已经有东西运行了. 运行 

docker ps

 (像在linux主机上运行 ps) - 你什么容器也看不到,一个进程都没有(这老外~尼玛). Run 

docker ps -a

, 见证奇迹的时刻!

CONTAINER ID    IMAGE           COMMAND         CREATED              STATUS      PORTS       NAMES
8ea31697f021    ubuntu:12.04/bin/bash       About a minute ago   Exit0                  loving_pare

我们已经可以看到一个在运行的 

/bin/bash

, 但是没有任何进程在维持它的执行.一个docker容器是伴随进程运行的.

先记住上面说的. 让我们继续操作docker,来玩一会儿:

docker run -t -i ubuntu /bin/bash

现在你可以看到你已经以root的身份登录到了docker 容器!

这些命令都在做什么?

  •  - 运行一个容器 docker run

  • -t - 分配一个(伪) tty

  • -i - 开发输入(so we can interact with it)

  • ubuntu - 使用ubuntu基础镜像

  • /bin/bash - 运行bash shell

跟进改变

使用 (ctrl+d or type exit) 来退出然后继续执行 docker ps -a . :

CONTAINER ID    IMAGE           COMMAND         CREATED              STATUS      PORTS       NAMES
30557c9017ec    ubuntu:12.04/bin/bash       About a minute ago   Exit127                elegant_pike
8ea31697f021    ubuntu:12.04/bin/bash       22 minutes ago       Exit0                  loving_pare

复制粘贴现在运行的容器 ID (30557c9017ec in my case). 运行

docker diff <container id>
core@localhost ~ $ docker diff 30557c9017ec
A /.bash_history
C /dev
A /dev/kmsg

我们可以看到我们仅仅登录了 bash之后的改变,创建了  .bash_history file, a /dev directory and a/dev/kmsg file. 这是细微的变化,确实伟大的存在! Docker 会跟踪我们在容器中的所有改动. 事实上,docker就是运行我们创造改动,提交改动,发布改动,然后将改变带到任何地方. 这就是我们使用docker所依赖的基础.

下面我们就来安装点新玩意到容器中,让把它们打包成我们自己的.

# Get into Bash
docker run -t -i ubuntu /bin/bash

# Install some stuff
apt-get update
apt-get install -y git ack-grep vim curl wget tmux build-essential python-software-properties

等这些都运行完了之后,退出,再执行 

docker ps -a

 . 在复制最新的ID 

docker diff <Container ID>
core@localhost ~ $ docker diff 5d4bdae290a4> A TON OF FILE LISTED HERE

当然又有很多的新文件被添加. 下面我们就来打包我们的版本,为了以后更好的使用.我们将提交修改,给它命名并打上标签。 我们将使用: 

docker commit <Container ID> <Name>:<Tag>


core@localhost ~ $ docker commit 5d4bdae290a4 fideloper/docker-example:0.1
c07e8dc7ab1b1fbdf2f58c7ff13007bc19aa1288add474ca358d0428bc19dba6  # You'll get a long hash as a Success message

我们来看看我们打包好的镜像. Run 

docker images
core@localhost ~ $ docker images

REPOSITORY                 TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
fideloper/docker-example   0.1                 c07e8dc7ab1b        22 seconds ago      455.1 MB
ubuntu                     13.109f676bd305a46 weeks ago         178 MB
ubuntu                     saucy               9f676bd305a46 weeks ago         178 MB
ubuntu                     13.04               eb601b8965b8        6 weeks ago         166.5 MB
ubuntu                     raring              eb601b8965b8        6 weeks ago         166.5 MB
ubuntu                     12.105ac751e8d6236 weeks ago         161 MB
ubuntu                     quantal             5ac751e8d6236 weeks ago         161 MB
ubuntu                     10.049cc9ea5ea5406 weeks ago         180.8 MB
ubuntu                     lucid               9cc9ea5ea5406 weeks ago         180.8 MB
ubuntu                     12.049cd978db300e6 weeks ago         204.4 MB
ubuntu                     latest              9cd978db300e6 weeks ago         204.4 MB
ubuntu                     precise             9cd978db300e6 weeks ago         204.4 MB

你已经注意到了那么多的ubuntu啊. 当我第一次加载使用ubuntu基础镜像的时候,就已经加载相应的ubuntu标记,更多知识请浏览 Docker index.

非常有趣,不管怎样我们已经用了自己的镜像 叫做 

fideloper/docker-example

 并且有个版本标签0.1!

使用 Dockerfile 建立一个服务器

让我们使用Dockerfile来创建一个服务器,大概就是通过一个配置文件,让git和wget自动的完成这个服务器环境的搭建工作.

创建一个目录,然后cd进去,因为我们要使用nginx服务器,就需要有个默认的配置文件.

创建一个叫做 default 的配置文件:

server {
root /var/www;
index index.html index.htm;# Make site accessible from http://localhost/
server_name localhost;

location /{# First attempt to serve request as file, then# as directory, then fall back to index.html
try_files $uri $uri//index.html;}}

这就是nginx的基础配置了.

下面我们创建一个 Dockerfile 并添加如下内容, 修改镜像FROM为我们自己的镜像(多么happy):

FROM fideloper/docker-example:0.1

RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe">/etc/apt/sources.list
RUN apt-get update
RUN apt-get-y install nginx

RUN echo "daemon off;">>/etc/nginx/nginx.conf
RUN mkdir /etc/nginx/ssl
ADD default/etc/nginx/sites-available/default

EXPOSE 80

CMD ["nginx"]

这些东西有都是什么的?

  • FROM 告诉docker使用哪个镜像,用什么版本

  • RUN 以root用户身份来执行linux命令

  • ADD 复制文件到容器内

    • 这是一个非常方便来管理配置文件的方式

  • EXPOSE 暴露端口给主机,也可以添加更多的端口比如: EXPOSE 80 443 8888

  • CMD 执行一个命令 (但不是以 sh -c). 这通常是来运行长时间的进程,但是我们只是来启动nginx.

    • 在生产环境中,我们想看到nginx在运行失败的情况下的表现

保存好这些,让我们来build一个新镜像

docker build -t nginx-example .

如果它正常工作了并结束 

Successfully built 88ff0cf87aba

 (这个容器ID并不一定都是一样的).

检查一下你有获得的什么镜像 docker images:

core@localhost ~/webapp $ docker images
REPOSITORY                 TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
nginx-example              latest              88ff0cf87aba35 seconds ago      468.5 MB
fideloper/docker-example   0.1                 c07e8dc7ab1b        29 minutes ago      455.1 MB
...other Ubuntu images below ...

再运行一下 docker ps -a:

core@localhost ~/webapp $ docker ps -a
CONTAINER ID        IMAGE                   COMMAND                CREATED              STATUS    PORTS    NAMES
de48fa2b142b        8dc0de13d8be/bin/sh -c #(nop) CM   About a minute ago   Exit 0             cranky_turing84c5b21feefc2eb367d9069c/bin/sh -c #(nop) EX   About a minute ago   Exit 0             boring_babbage3d3ed53987ec77ca921f5eef/bin/sh -c #(nop) AD   About a minute ago   Exit 0             sleepy_brattain
b281b7bf017f        cccba2355de7            /bin/sh -c mkdir /et   About a minute ago   Exit0             high_heisenberg
56a84c7687e9        fideloper/docker-e.../bin/sh -c #(nop) MA   4 minutes ago        Exit 0             backstabbing_turing... other images ...

你可以看到 每一行Dockerfile中的指令, 一个新的容器已经诞生 if that line results in a change to the image used. Similar(ish) to version control! (Also, how funny is the name "backstabbing_turing"?)(这一段很难明白?难道又是老外在用,英语文化开玩笑,希望给个指点)

最后,执行这个web服务

让我们来运行web服务器! 使用 

docker run -p 80:80 -d nginx-example

 (确定你要使用的名称).

-p 80:80 经容器的80端口 绑定到了主机上 通过curl localhost 或者访问主机ip就可以看到运行的状态

 

core@localhost ~/webapp $ docker run -d nginx-example
73750fc2a49f3b7aa7c16c0623703d00463aa67ba22d2108df6f2d37276214cc# Success!

core@localhost ~/webapp $ docker ps
CONTAINER ID        IMAGE                  COMMAND    CREATED          STATUS         PORTS     NAMES
a085a33093f4        nginx-example:latest   nginx      2 seconds ago    Up2 seconds   80/tcp    determined_bardeen

现在我们使用 docker ps 而不是 docker ps -a - 我们可以看到容器中正在运行的进程(回想最初的命令). 执行 curl localhost:

core@localhost ~/webapp $ curl localhost/index.htmld
<html><head><title>500InternalServerError</title></head><body bgcolor="white"><center><h1>500InternalServerError</h1></center><hr><center>nginx/1.1.19</center></body></html>

我们看到了nginx的回应,但是是500错误. 这是因为我们并没有一个index.html 资源文件让nginx返回. 让我们停止这个容器的运行 docker stop <container id>:

core@localhost ~/webapp $ docker stop a085a33093f4
a085a33093f4

来解决一下这个问题,我们来共享一个目录在主机和容器之间,这样就用个index.html了,首先创建一个index.html在我们想要贡献的目录中

# I'm going to be sharing the /home/core/share directory on my CoreOS machine
echo "Hello, Docker fans!">>/home/core/share/index.html

然后我们再启动这个容器(注意命令):

docker run -v /home/core/share:/var/www:rw -p 80:80 -d nginx-example
  • docker run
    
     - 启动容器
  • -v /path/to/host/dir:/path/to/container/dir:rw
    
     - 指定一个目录共享到容器,并且指定权限为读写,也可以指定为只读
  • -p 80:80
    
     - 绑定端口.
  • -d nginx-example
    
     启动nginx-example的容器,由于在配置文件的CMD中已经指定启动了 
    nginx ,在容器启动时nginx即启动

运行 curl localhost:

core@localhost ~/webapp $ curl localhost
Hello,Docker fans!

...或者直接访问主机ip

 

注意这个ip是主机的ip. 我已经在 Vagrantfile  中指定了ip。我不需要知道要转发的ip究竟是什么? 当时可以通过这个命令来看到 

docker inspect <Container ID>
core@localhost ~/webapp $ docker inspect a0b531aa00f4
[{"ID":"a0b531aa00f475b0025d8edce09961077eedd82a190f2e2f862592375cad4dd5","Created":"2014-03-20T22:38:22.452820503Z",... a lot of JSON ..."NetworkSettings":{"IPAddress":"172.17.0.2","IPPrefixLen":16,"Gateway":"172.17.42.1","Bridge":"docker0","PortMapping":null,"Ports":{"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"80"}]}},... more JSON ...}]

链接不同的容器

现在已经可以将多个容器联合在一起了 。能将容器联合在一起,是一项非常重要的技能. 举个例子, 比如你的web容器想要链接你的数据库容器. 链接可以让多个分支应用,独立于你的应用。

举个例子:

启动一个容器,并且取个有意义点的名字 (在这里就叫, mysql):

docker run -p 3306:3306-name mysql -d some-mysql-image

启动你的web容器 将它和另外一个容器链接在一起 -d name:db (where db is an arbitrary name used in the container's environment variables):

docker run -p 80:80-link mysql:db -d some-application-image
在例子中, 

some-application-image
 有这样的环境变量设置
DB_PORT_3306_TCP_ADDR=172.17.0.8
 and 
DB_PORT_3306_TCP_PORT=3306
 用于在应用中的设置

这是一个mysql的dockerfile MySQL Dockerfile

结论

我们就是可以这么简单的来创建一台服务器,添加应用代码,控制应用。执行环境中的每一件事都在你的掌控之中

通过这种方式,我们可以 跳过复杂的环境设置,专心于自己的代码.

P.S. - Tips and Tricks

 

如果执行完了这次体验,你想自己做点什么,并且清理掉之前的docker 容器:

  • 删除容器: 
    
    docker rm <Container ID>
  • 删除所有容器: 
    
    docker rm $(docker ps -a -q)
  • 移除镜像: 
    
    docker rmi <Container ID>
  • 移除所有镜像: 
    
    docker rmi $(docker ps -a -q)

Note: 在移除镜像前,你必须删除掉,所有依赖于该镜像的容器

加入你已经完整体验了上面的教程,那么就已经对docker有了很系统的了解,因为docker 无非就是用来构建一个可移植环境,为了能有进一步的提高,推荐进行下面的学习

  • docker操作命令,主要熟悉一个常用的操作,docker的命令不多,基本上都会用到,推荐:docker命令详解

     

  • Dockerfile的编写,用于建立一个属于自己的定制化的docker image镜像,docker建立镜像很简单,主要是要能够编写Dockerfile进行定制,推荐:Dockerfile说明文档