Docker 入门教程

该教程仅对docker常用操作进行了总结归纳

一、 什么是Docker

Docker是2013年,dotCloud用Golang开发的基于LXC的高级容器引擎

1.1 什么是LXC

Linux Container的简写。可以提供轻量级的虚拟化,以便隔离进程和资源,而且不需要提供指令解释机制以及全虚拟化的其他复杂性。相当于C++中的NameSpace

1.2 什么是容器

是一个相对独立的运行环境

1.3 容器和虚拟机的区别

1

Docker 虚拟机
启动速度 秒级 分钟级
启动方式 启动应用 启动Guest OS + 启动应用
交付、部署 Docker镜像(Dockerfile) 虚拟机镜像
更新 修改Dockerfile或发布新镜像 向虚拟机推送补丁升级包
操作系统 Linux x64 Linux、Windows、Mac OS等
磁盘占用率
性能利用率
成熟度 发展中
稳定性 发展中
隔离性
数据保留 删除后销毁 删除后销毁

二、 Docker的3个概念

Docker Git OOD
Image Repository Code Class
Container Local Code Instance
Registry GitHub/GitLab

2.1 Image

  • 镜像,Docker Image是一系列操作的集合,每一组操作形成一个layer,每个layer有一个id,并说明该layer依赖的前一个layer的id,多个layer堆叠形成Image,注意理解一组操作的意义

    18

    2

  • Image是静态的,不能直接使用。像面向对象中的类,也像Git中放在仓库中的代码

  • 可以给Image设置Repository,同一个Image ID可以有多个Repository,Repository的命名规范是“Registry Name/Image Name”,若Registry Name为空,则默认Registry为Docker HUB3

  • 可以给Image设置Tag,同一个Image ID可以有多个标签Tag,若tag为空,则默认Tag为latest,表示最新的镜像4

  • Image可以在本地通过Container Commit生成,亦可用Dockerfile生成,还可以从Registry Pull下来

2.2 Container

  • 容器,Docker Container 是Image的实例化对象
  • Container是动态的,可以使用的。像面向对象中的对象,也像Git中被pull到本地的代码,而且代码可以编译和运行
  • Container经过一系列操作后,并不会影响Image,如果需要生成新的Image,需要做类似git的流程对Container进行Commit操作
  • Container删除会导致Container中的数据消失,从Image启动新的Container是一个全新的容器,不能发现或共享其他Container中的数据5

2.3 Registry

  • 仓库,Docker Registry 是存放Image的地方
  • Registry像GitHub、Gitlab一样,有官方提供的Docker HUB,也可以自己搭建
  • Registry里面的项目有公开和私有两种权限,公开项目下的镜像可以被任何人pull,私有项目的镜像只能被Registry分配权限的用户pull6

三、 Docker 版本与安装

7

8

四、 Docker常用指令

4.1 Image 指令

  • docker images

    查询所有镜像

    9

  • docker tag

    “复制”一个Image副本,并重新命名(包括Repository Name、Image Name和Tag)

    • docker tag f06 my/redis:4

      f06a5773f01e 可以简写开头的几位

    • docker tag redis:latest redis:4

    • docker tag redis my/redis

      表示把redis:latest 复制为 my/redis:latest

  • docker rmi

    删除指定的镜像

    10

    • docker rmi redis:4

      删除redis:4 这个Image,若该Image ID在本地有其他名称或Tag,则只移除这个Tag

    • docker rmi f06

      删除Image ID缩写为f06的 Image,该Image ID的所有副本(不同的Repository、Image Name和Tag)都会被删除

    • docker rmi -f f06

      默认情况下,rmi指令只能删除没有Container的Image;通过-f 参数可以强制删除有Container但Container是停止状态的Image;不能直接删除有运行状态Container的Image,必须先停止、删除Container后再删除Image

  • docker history

    查询指定镜像的操作历史

    11

    • docker history f06

      查看镜像的所有历史操作,甚至可以通过该指令看出镜像是如何构建出来的,但对于Container的操作在这里无法体现

  • docker inspect

    查询指定镜像的详细信息

    12

    • docker inspect f06

4.2 Container 指令

  • docker run

    从Image运行一个新Container

    • docker run --name myRedis f06

      从Image运行容器并通过–name 命名为myRedis

    • docker run -it f06 bash

      从Image运行容器,随机名称,-it 是 -i和-t,表示交互并重定向到控制台,bash表示容器入口,不填则进入镜像的默认入口

    • docker run -d f06

      -d 表示后台执行

  • docker ps

    查看容器列表

    13

    • docker ps

      查看仅在运行状态的Container

    • docker ps -a

      查看所有状态的Container

  • docker inspect

    查看指定容器的详细信息

    • docker inspect myRedis
  • docker exec

    14

    进入指定的容器

    • docker exec -it myRedis bash

      以交互模式进入myRedis 容器的bash

    • docker exec -u root -it myRedis bash

      同上,但以root用户登录

  • docker stop/start/restart

    停止/启动/重启容器

    • docker stop myRedis
    • docker start myRedis
    • docker restart myRedis
  • docker rm

    删除指定的容器

    • docker rm myRedis

      删除名为myRedis 且状态为退出、停止的容器

    • docker rm 3c7

      删除ID简写为3c7且状态为退出、停止的容器

    • docker rm -f myRedis

      强制删除名为myRedis 的容器,不管容器是否正在运行

4.3 Registry 指令

  • docker login

    登录指定的docker registry

    • docker login -u test -p xxx hub.docker.com

      以用户test,密码为xxx登录Docker HUB

  • docker pull

    从指定的docker registry pull指定的Image,对于私有镜像,必须先用docker login登录registry

    • docker pull test/redis:4

      从Docker HUB的test用户拉取redis:4 镜像

    • docker pull a.b.com/test/redis:4

      从a.b.com这个registry的test用户拉取redis:4 镜像

  • docker push

    把指定Image push到docker registry,必须先用docker login登录registry

    • docker tag redis:latest a.b.com/test/common/redis:4

    • docker push a.b.com/test/common/redis:4

      把redis:lastet复制为a.b.com/test/common/redis:4,并push到a.b.com的test用户的common项目下,镜像名为redis,Tag为4

4.4 练习

  • 从Docker HUB pull一个Nginx Image
  • 运行Nginx Container,并访问默认页面
  • 进入容器删除 /usr/share/nginx/html/index.html ,提交容器到新镜像
  • 关闭容器,从旧镜像启动Nginx Container,访问页面并观察
  • 关闭容器,从新镜像启动Nginx Container,访问页面并观察
  • 注册Docker HUB账号,将新镜像提交至自己的Docker HUB

五、 Docker Volume

由于Docker Container释放后不能保存数据,并且Container之间不能共享数据,所以需要“外接一个存储”用于数据的持久化和共享。这就是挂载卷Volume

PS:因为非Linux环境的Docker都是基于Linux虚拟机,所以在非Linux环境下使用Docker Volume需注意以下几点:

  • 如果使用DockerToolBox,需要给虚拟机挂载共享目录,只有这个目录可以作为Volume
  • 如果使用https://github.com/sonicrang/Docker_FrontEnd 我已经在控制台封装好设置虚拟机共享目录的功能,方法为控制台输入1,即可将本机目录例如”e:\test”挂到虚拟机”/develop”下,只有”/develop”目录可以作为Volume

挂载示例:

  • docker run -v /develop/nginx/log:/var/log/nginx --name myNginx nginx

    将宿主机的”/develop/nginx/log” 空目录挂载到容器的”/var/log/nginx”,启动容器后,对于linux系统,可以在宿主机”/develop/nginx/log”看到容器内的日志文件;对于使用DockerToolBox或者Docker_FrontEnd,会在本机的”e:\test”看到容器中的内容。当容器删除后,宿主机目录中的日志文件会被保留下来

    15

  • docker run -v nginx:/var/log/nginx --name myNginx nginx

    将宿主机的空目录”/var/lib/docker/volumn/nginx” 挂载到容器的”/var/log/nginx”,也就是说宿主机的目录可以填写为相对目录,相对于一个docker的默认安装目录

  • docker run -v /var/log/nginx --name myNginx nginx

    将宿主机的空目录”/var/lib/docker/volumn/containerID” 挂载到容器的”/var/log/nginx”,也就是说宿主机的目录可以不填,默认为docker安装目录对应的containerID目录

  • docker run -v /develop/nginx/html:/usr/share/nginx/html --name myNginx nginx

    注意在之前的例子中,我们把空目录挂载到容器的空文件夹中(因为log是运行容器后才产生的)。但在这个例子中,我们把空目录挂载到容器的非空文件夹”/usr/share/nginx/html”,由于挂载源是空文件夹,会覆盖掉挂载目标也就是”/usr/share/nginx/html”的内容,这样就导致nginx的html文件丢失,所以切记不要用一个空目录挂载到容器在运行前的非空目录下,如果我们要在挂载源控制nginx的html文件内容,那就必须要保证挂载源非空且存在html文件

  • docker run -v /develop/nginx/html/index.html:/usr/share/nginx/html/index.html:ro --name myNginx nginx

    将宿主机的”/develop/nginx/html/index.html”文件以只读的形式挂载到容器的”/usr/share/nginx/html/index.html”

六、 容器网络

6.1 访问容器

  • 对于linux下的docker,本机IP+端口就可以访问容器,比如127.0.0.1就可以访问nginx容器
  • 对于DockerToolBox和Docker_FrontEnd,要访问虚拟机的IP+端口,默认为192.168.99.100

但是此时输入URL还不可以访问容器,因为端口还没有映射

6.2 端口映射

  • docker run -p 80:80 --name myNginx nginx

    把容器的80端口映射到宿主的80端口,这样就可通过宿主机IP:80访问nginx容器

  • docker run -p 8080:80 --name myNginx nginx

    把容器的80端口映射到宿主的8080端口,这样就可通过宿主机IP:8080访问nginx容器

  • docker run -p 8080:80 -p 8081:81 --name myNginx nginx

    把容器的80端口映射到宿主的8080端口,同时把容器81端口映射到宿主的8081端口

  • docker run -p 8080-8085:80-85 --name myNginx nginx

    把容器的80、81、82、83、84、85端口映射到宿主的8080、8081、8082、8083、8084、8085端口

  • docker run -P --name myNginx nginx

    把容器的所有开放端口随机映射到宿主

6.3 容器间通讯

相同宿主机下有两个容器A、B

容器A内部端口80映射到宿主的8080

容器B访问容器A的方法如下:

  • 宿主IP:8080

    curl 192.168.99.100:8080

  • 容器A IP:80

    curl 172.17.0.2

    容器A的IP获取需要使用命令docker inspect A

    16

七、 Dockerfile

在2.1小结我们提到Image其实是一系列操作的层堆叠起来。但是传统的commit方式不能很好的呈现Image是如何构建的。试想一个场景,搭建一个软件运行环境的Image,这个Image会随着业务、时间不断更新,如果运维人员想看Image究竟是怎样堆叠起来的,只能用history指令,不利于查看为维护。所以这又引出了2.1小结提到的另一个概念Dockerfile

7.1 Dockerfile编写方式

下面的示例是将一个golang程序打包成Image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# FROM关键字 说明原始镜像
FROM golang:1.9.2-alpine3.6

# RUN关键字用于执行命令
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep

# COPY Dockfile上下文目录的Gopkg.lock Gopkg.toml 到镜像的/go/src/project目录下,注意如果是目录,最后不要忘了/
COPY Gopkg.lock Gopkg.toml /go/src/project/
# WORKDIR 关键字相当于切换目录
WORKDIR /go/src/project/
RUN dep ensure -vendor-only

# COPY Dockfile上下文目录的所有文件到镜像的/go/src/project目录下
COPY . /go/src/project/
RUN go build -o /bin/project

# 暴露端口80,参考6.2小结,如果使用-P映射(大写P),用以告诉docker,容器的80端口是开放的,可以映射出去
EXPOSE 80
# 设置入口,相当于通过镜像启动容器后,进入"/bin/project",该配置可以被docker run --entrypoint xxx app 强行覆盖
ENTRYPOINT ["/bin/project"]
# 执行命令,该配置可以被docker run app xxx 强行覆盖
CMD ["--help"]

什么是Dockerfile的上下文?需要了解Dockerfile的构建方式

7.2 Dockerfile构建方式

  • docker build -t myRepo/myAPP:latest .

    注意最后的那个点”.”,查找当前目录名为Dockerfile的文件,构建Image并命名为myRepo/myAPP:latest

  • docker build -f myRepo/myAPP:latest -f myDockerfile

    查找当前目录名为myDockerfile的文件,构建Image并命名为myRepo/myAPP:latest

在上面两个例子中,Dockfile的上下文就是指Dockerfile文件所在的目录,先看下图:

17

左右两幅图目录结构不同,如果Dockerfile具有同样的内容:

1
2
FROM XXX
COPY . /target

那么左边的目录结构,最终被放到镜像”/target”目录中的文件应该只有”run.sh”这个文件,而右边的目录结构,最后在”/target”中的是”document”和”source_code”的文件夹及内容。

八、 Docker Compose

试想一个业务场景,我们有一个服务容器A需要连接数据库,把数据库装到A的镜像里不是一个明智的选择,一般情况下我们会再搭建一个容器B作为数据库,那么这时,两个容器如何关联?这里的关联不仅包括如何保证两个容器如何同时管理,还包括两个容器如何通讯。这就引出来容器编排的概念,我们需要对容器进行一系列的调整,以满足我们的业务需要。

Docker Compose 需要额外安装,详情见https://docs.docker.com/compose/install/

Docker Compose是用于管理多容器的Docker应用。看一个示例:

docker-compose.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 标明用docker compose的哪个版本,不同版本兼容的指令有区别
version: '3'
# 服务编排
services:
# 服务名称
nginx:
# 镜像地址
image: nginx
# 容器名称,不填则为“服务名_序号”
container_name: nginx
# 设置volume
volumes:
- nginx.conf:/etc/nginx/nginx.conf:ro
# 端口映射
ports:
- "8080:80"

api:
image: xxx.com/api:latest
container_name: api

nginx.conf

1
2
3
4
5
6
7
server{
listen 80;
server_name xxx.yyy.com;
location / {
proxy_pass http://api;
}
}

上面的服务编排就是对api容器做了一个nginx反向代理,可以看到使用docker-compose可以让容器间的通讯更加简单,直接用Container Name加Container内部端口即可。

使用docker-compose必须将文件命名成”docker-compose.yaml”,使用方法如下:

首先进入”docker-compose.yaml”文件所在目录

  • docker-compose up -d

    根据编排生成所需的容器并启动,-d表示后台执行

  • docker-compose start

    启动编排的容器

  • docker-compose stop

    停止编排的容器

  • docker-compose restart

    重启编排的容器

  • docker-compose pull

    强制拉取编排所需的镜像,因为在上例中,api使用的是latest镜像,当又有新的lastest镜像被push到Registry时,如果本地又存在一个旧的api:latest,那么docker compose 不会自动更新镜像,需要强制用”docker-compose pull”强制拉取,再执行”docker-compose up”

  • docker-compose build

    docker compose的容器镜像还可以直接用Dockerfile

    1
    2
    3
    4
    5
    version: '3'
    services:
    api:
    build: ./
    container_name: api

    表示编排容器时,需要先在本地用Dockerfile构建一个镜像,如果Dockerfile文件发生改变,而本地又有旧镜像,那么docker compose 同样不会自动更新镜像,需要执行”docker-compose build”强制构建,再执行”docker-compose up”

作者

Wu Rang

发布于

2018-07-31

更新于

2021-12-06

许可协议

评论