Docker 入门教程
该教程仅对docker常用操作进行了总结归纳
一、 什么是Docker
Docker是2013年,dotCloud用Golang开发的基于LXC的高级容器引擎
1.1 什么是LXC
Linux Container的简写。可以提供轻量级的虚拟化,以便隔离进程和资源,而且不需要提供指令解释机制以及全虚拟化的其他复杂性。相当于C++中的NameSpace
1.2 什么是容器
是一个相对独立的运行环境
1.3 容器和虚拟机的区别
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,注意理解一组操作的意义
Image是静态的,不能直接使用。像面向对象中的类,也像Git中放在仓库中的代码
可以给Image设置Repository,同一个Image ID可以有多个Repository,Repository的命名规范是“Registry Name/Image Name”,若Registry Name为空,则默认Registry为Docker HUB
可以给Image设置Tag,同一个Image ID可以有多个标签Tag,若tag为空,则默认Tag为latest,表示最新的镜像
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中的数据
2.3 Registry
- 仓库,Docker Registry 是存放Image的地方
- Registry像GitHub、Gitlab一样,有官方提供的Docker HUB,也可以自己搭建
- Registry里面的项目有公开和私有两种权限,公开项目下的镜像可以被任何人pull,私有项目的镜像只能被Registry分配权限的用户pull
三、 Docker 版本与安装
- Docker分为CE和EE版,CE版免费,EE收费、注重安全并能给企业提供技术支持
- Docker在各个操作系统上的安装方式见官网 https://www.docker.com
- 在Windows下亦可使用基于DockerToolBox的项目 https://github.com/sonicrang/Docker_FrontEnd ,控制台输入5直接进入Docker环境
四、 Docker常用指令
4.1 Image 指令
docker images
查询所有镜像
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
删除指定的镜像
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
查询指定镜像的操作历史
docker history f06
查看镜像的所有历史操作,甚至可以通过该指令看出镜像是如何构建出来的,但对于Container的操作在这里无法体现
docker inspect
查询指定镜像的详细信息
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
查看容器列表
docker ps
查看仅在运行状态的Container
docker ps -a
查看所有状态的Container
docker inspect
查看指定容器的详细信息
docker inspect myRedis
docker exec
进入指定的容器
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”看到容器中的内容。当容器删除后,宿主机目录中的日志文件会被保留下来
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
七、 Dockerfile
在2.1小结我们提到Image其实是一系列操作的层堆叠起来。但是传统的commit方式不能很好的呈现Image是如何构建的。试想一个场景,搭建一个软件运行环境的Image,这个Image会随着业务、时间不断更新,如果运维人员想看Image究竟是怎样堆叠起来的,只能用history指令,不利于查看为维护。所以这又引出了2.1小结提到的另一个概念Dockerfile
7.1 Dockerfile编写方式
下面的示例是将一个golang程序打包成Image
1 | # FROM关键字 说明原始镜像 |
什么是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文件所在的目录,先看下图:
左右两幅图目录结构不同,如果Dockerfile具有同样的内容:
1 | FROM XXX |
那么左边的目录结构,最终被放到镜像”/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 | # 标明用docker compose的哪个版本,不同版本兼容的指令有区别 |
nginx.conf
1 | server{ |
上面的服务编排就是对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
5version: '3'
services:
api:
build: ./
container_name: api表示编排容器时,需要先在本地用Dockerfile构建一个镜像,如果Dockerfile文件发生改变,而本地又有旧镜像,那么docker compose 同样不会自动更新镜像,需要执行”docker-compose build”强制构建,再执行”docker-compose up”
Docker 入门教程