今天对docker做一个梳理。关于docker的安装和初步使用,上周已经实践过一次,今天就从安装完成后开始逐步梳理。包括使用前的一些准备工作和单步跟踪一个docker项目的全生命周期,从拉取镜像、运行容器、编辑容器到打包镜像和部署生产。
首先说一下wsl环境下ubuntu操作docker的问题,本来以为有了wsl就方便多了,但微软对wsl1的linxu子系统中做了很多折中,导致docker无法运行容器。查看了类似问题,按照说明将wsl升级到wsl2,但升级后出现子系统都无法启动的问题,可能是家庭版的原因,暂时只能先放一放,等以后有专业版windows再试就容易多了。既然wsl用不了docker,意义也不大了,还是换回虚拟机上的ubuntu。
1. 准备工作
相关指令
- 将当前用户添加到docker用户组:sudo adduser $USER docker
- 查看当前用户所属用户组:id $USER
- 启动|关停|重启docker:sudo service docker start|stop|restart
- 查看docker服务的状态:sudo service docker status
- 查看docker状态:docker info
docker安装好后,运行docker需要使用sudo提升权限,如果想让当前用户直接就能使用docker,需要将当前用户添加到docker用户组。可以cat /etc/group | grep docker查看一下,返回docker:x:999:的用户组信息。其中第一个docker是组名,x是密码的掩码,999是这个组创建时给的编号,后面跟的是用户名,因为现在还没有为这个组添加任何用户,所以冒号后面是空的。输入groupmod三连击tab,可以输出系统里的所有组,查看一下当前用户所属的组有哪些,输入id $USER,($USER是环境变量,你可以echo $USER看一下,就是你登录的用户名,你也可以直接输入用户名取代这个环境变量)。id指令输出了所查询用户的所有组名,目前还没加入docker组。输入sudo adduser $USER docker,再用id指令查看一下,当前用户已经加入到docker组了。以后再运行docker指令就无需使用sudo提权。
在进行下一步之前别忘了修改docker的hub源为国内源,具体方法在上周的日志中已经介绍,也可点此处温故。修改源重启docker后,使用docker info查看一下docker当前的状态,最后一段Registry Mirrors中是否列出了刚才修改的hub源?如果这条指令报如下错误“Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?”,这说明docker没有启动,可以使用sudo service docker start试一下,再用sudo service docker status看一下是否启动成功。
2. 拉取镜像
相关指令
- 查看本地镜像列表:docker image ls
- 搜索docker hub上的镜像:docker search [--filter "xxx=xx"] XXX
- 拉取镜像:docker image pull XXX:tag
- 删除本地镜像:docker rmi XXX:tag|IMAGE_ID
万丈高楼平地起,首先要把地基选好,这个地基就是镜像。在准备工作中,我们已经切换了docker hub为国内源,这个源就是提供地基的镜像仓库,得益于docker社区的人丁兴旺,镜像仓库中已经储备了非常丰富的镜像,能几乎满足各行业的地基需求。我们只需在仓库中搜索自己需要的镜像,再以此为基础做进一步的修改和配置。为何不从头构建自己的镜像呢?原因有二,一是效率不多说,二是镜像仓库中提供的公共源是经过安全验证和精简化的,简而言之就是如果我们自己动手构建出相同目的的镜像,在体积和性能上往往都比不过镜像仓库中经过考验的公共镜像。
根据自己的需求,首先到docker hub上搜索需要的镜像,比如我们要搭建一个nginx反向代理服务,输入docker search nginx指令,搜索公共镜像库,得到一堆返回信息。如果想增加一些过滤信息,可以添加--filter参数,如--filter "is-official=true"则仅仅返回官方版,--filter "stars=500"则返回获星数大于500的镜像。有人要问,如果这个镜像不错,我是不是也可以去点个星?当然可以了,移步docker hub官网,如果要点星,首先得注册一个帐号,然后再在左上角的搜索框中搜索想要的镜像,这个是可视化的搜索方式,可以查看镜像的说明、查看镜像各版本的tag号,可以给心怡的镜像点★。如果仅仅是查看版本和tag,不注册帐号也是可以的。
这里提到了tag,tag是镜像的版本标签,同一个镜像会不停迭代版本,通过tag来标记版本,如果在拉取镜像时不指定tag,则默认拉取tag为latest的版本,通过刚才的官网查询,你也可以选择自己需要的版本,比如stable也是不错的选择。拉取镜像的指令是docker image pull nginx:stable,国内镜像速度还是可以的,很快就拉取完毕,通过docker image ls再查看一下,本地的镜像仓库已经增加了一条nginx镜像。
如果想删除本地的镜像,可以通过docker rmi IMAGE_ID|REPOSITORY:TAG指令。rmi是移除镜像的意思,后面跟的参数可以是镜像的ID号,也可以是镜像的名称:tag,但移除镜像前先要确保基于该镜像的容器已经全部关闭,否则会报错。这里推荐通过镜像名:tag的组合参数方式来移除镜像,一是便于人类操作,二是存在两个镜像ID号重复的情况,比如有两个nginx镜像,一个tag为stable,一个tag为1.19,这两个镜像其实是相同的,具有相同的IMAGE_ID,此时如果通过IMAGE_ID来删除,则会报错。
3. 运行容器
利用本地镜像库中的镜像启动一个容器,docker run --name mynginx -d -p 81:80 nginx:stable,启动成功后会返回一串ID号,这串号码的前12位就是这个容器的ID号。这段命令中,通过--name指定了容器别名,通过-d指明属于后台进程,不会随终端关闭而关闭,-p 81:80指定了将本地81端口映射给容器的80端口,最后指明镜像的名字和tag。可以通过docker ps查看活动的容器,也可以再打开一个终端,输入docker stats,结果将以列表形式不断刷新当前所有活动容器的状态,类似于打开了一个实时监控窗口。
通过docker stop mynginx可以关闭这个容器。通过docker rm mynginx可以删除这个容器,删除之前先要保证容器处于关闭状态。只有与镜像关联的所有容器都被删除后,镜像才能被删除。
4. 编辑容器
获取容器内系统的bash,可通过docker exec -it mynginx bash指令切换为容器内系统的bash。
在宿主机上通过docker cp指令,可以实现宿主机与docker容器的文件互传,在宿主机桌面上打开终端,输入docker cp default.conf mynginx:/etc/nginx/conf.d/default.conf,则实现了用宿主机桌面上的default.conf配置文件覆盖容器中nginx配置文件的目的。当然这个桌面上的配置文件是提前准备好的,比如为了实现浏览器地址栏URL不改变的隐式跳转,可以这样配置nginx:
server{
listen 80;
server_name 192.168.31.111;
location / {
proxy_pass https://www.baidu.com;
}
}
其中那串IP地址是我虚拟机上ubuntu的局域网地址,配置文件覆盖后,通过前文介绍的获取容器bash的方法进入容器,执行nginx -s reload重启nginx,到ubuntu或者windows上用浏览器访问192.168.31.111:81,可以看到打开的内容是百度的首页,这就实现了nginx的反向代理服务。为什么这里是81端口?还记得前面run这个容器时所做的端口映射吗。
5. 打包容器
相关指令
- 导出容器:docker export 容器名 > 本地路径/xxx.tar
- 对应的导入镜像: docker import 本地路径/xxx.tar 镜像名:tag
- 将容器保存到镜像库:docker commit -a 作者 -m 描述 镜像名:tag
- 将镜像保存到本地:docker save 镜像名:tag > 本地路径/xxx.tar
- 对应的导入镜像:docker load < 本地路径/xxx.tar
- 查看镜像历史信息:docker history 镜像名:tag
- 查看镜像层文件:docker inspect 镜像名:tag
打包容器有两种方法,一种是直接直接将容器导出到本地磁盘,一种是将容器提交到本地镜像库,然后再从镜像库中导出镜像到本地磁盘。这两者初看似乎结果一样,都是实现了容器的本地持久化保存,实际还是有区别的。先来练手实践,后面再来分析它们的区别。
先是第一种,直接将容器导出到本地磁盘,docker export 容器名 > /home/xxx.tar,这里就将指定容器的快照持久化保存到本地/home/xxx1.tar文件中。从该文件还原镜像的指令是docker import /home/xxx1.tar 镜像名:tag。
第二种将容器持久化到本地镜像库,docker commit -a 作者 -m 描述 镜像名:tag,再将镜像库中的这个镜像导出到本地磁盘,docker save 镜像名:tag > /home/xxx.tar,这样也同样实现了容器转镜像再保存到本地的目的。从该文件还原镜像就不能用import了,import是与export对应的,与save对应的还原镜像指令是docker load < /home/xxx2.tar,load时不用指定镜像名和tag了,应为save保存时已经将原镜像的相关信息保存到xxx2.tar中了。
在导入镜像时可能会碰到错误,请查看一下是不是import和load用错了,不同的导出指令对应不同的导入指令。
这里再来细究一下两种打包容器的方法的区别。首先可以去看看刚才导出的两个压缩文件xxx1.tar和xxx2.tar,它们的文件大小是不一样的,export方式的xxx1.tar体积小于save方式的xxx2.tar,原因是export将当前容器的快照作为一层保存,而通过commit再save的方式保存了镜像的历史层信息。你可以试着将xxx1.tar和xxx2.tar再导入到镜像库,用指令docker history 镜像名:tar来查看下它们各自的历史信息,前者只有一条历史信息,后者保存有从基础镜像逐步构建的历史信息。查看镜像层信息的指令是docker inspect 镜像名:tag,从输出信息中RootFS:Layers属性中可以看到。镜像分层相当于镜像的开发里程碑,当从镜像生成容器后,会增加一层容器层,容器的所有操作都限制在这一层,下面各历史镜像层是不会被改动的,即使删除下层的文件,也只是在容器层增加了一个删除标记,所以越层删除反而会增加镜像的体积,而不会缩小体积。
理解了这两种打包方式的原理,就要审时度势的使用。如果确认当前容器已经最优,用于发布,则可以用export打包,所有层都将压缩到一起,丢失分层信息。如果使用敏捷开发和迭代部署,保留镜像分层信息可用于回滚。
导出、导入也已经实现了,docker的基本使用和部署也就掌握了。
6. 部署生产
docker非常适合于分布式快速部署,与之配合的有Swarm,docker集群管理平台。过去搭建大规模分布式系统,需要考虑很多细节问题,部署复杂,对运维的要求也很高。现在有了docker技术,配合Swarm,可以屏蔽很多分布式系统的底层细节问题,将重点集中到业务逻辑的拆分上,大大降低了运维的难度。这里有一篇文章,并发分布式架构演进之路,是淘宝从小规模发展到如今这样规模的架构演进过程,写得挺好,收藏于此。此生可能无缘部署如此规模的架构了,但其演进路上的某些里程碑是可以借鉴的,且如今有了docker这类容器化技术,比前人们的起点更高了。如何部署,需要的只是想象力和现实的业务需求量。当一切技术问题都不再是问题时,一张纸一支笔就能在架构设计的战场上运筹帷幄。
7. 2021.1.6补充:docker容器开机自启动
在使用docker run从镜像启动容器时,使用--restart参数来设置,--restart具体参数值详细信息:
- no:
容器退出时,不重启容器。 - on-failure:
只有在非0状态退出时才从新启动容器;在使用on - failure策略时,指定Docker将尝试重新启动容器的最大次数sudo docker run --restart=on-failure:10。默认情况下,Docker将尝试永远重新启动容器。 - always:
无论退出状态是如何,都重启容器。
如果创建容器时未指定 --restart=always ,可通过update命令更新容器:
docker update --restart=always 容器名