入门
镜像与容器
- 通过官网文档例子构建镜像,可以用镜像运行多个容器。
- 镜像的发布与拉取,类似于github操作
服务组
- 大的系统往往需要拆分成多个服务,每个服务构建成独立的镜像。而一个服务组可以统一管理各个服务的部署
- 通过docker-compose.yml配置,可以配置服务组,包含不同服务使用的镜像,容器数量,资源分配,重启策略,端口,网络
- docker stack deploy -c docker-compose.yml $services_name
- 这里的部署前提是docker swarm init,启动集群管理,当前机器为其中的一个节点
- 部署之前需要确认节点有配置所需要的镜像
集群管理
- docker swarm init 将当前节点作为管理节点
- 其他机器可以通过docker swarm join 加入集群,作为其中的一个节点,服务组部署启动的容器将被分布在集群的节点上
stack
- docker-compose.yml 引入可视化服务,redis服务
- volumes: 在redis容器内文件系统创建/data,通过映射到宿主系统的实际文件目录,redis数据将可以持久保存在宿主而不会因为容器删除而被删除
- 通过可视化我们可以直观地看到不同节点运行的服务
开发
开发最佳实践
- 选择合适的基础镜像,避免构建的镜像太大
- 多阶段构建,如果不使用多阶段构建,则尽量减少层数
- 创建一个公共基础镜像,docker只需要加载一次并缓存它
- 需要考虑基于生产镜像构建用于debug的镜像
- 添加有意义的镜像tag,包含版本信息,运行环境等
- 避免在容器存储层写数据,将会使容器膨胀,IO也不如使用数据卷高效
- 敏感数据使用secrets,非敏感使用configs
- 尽量使用swarm方式部署,可扩展,配置化,优雅重启,具备一些额外功能如secrets,configs,可以自动获取需要的镜像不需要手动拉取
- 使用CI/CD自动构建,打tag,测试
Dockerfile最佳实践
- FROM: 从指定的镜像创建一层,COPY: 从Docker客户端当前目录拷贝文件,RUN: 执行命令,CMD: 容器启动执行命令,容器启动时,添加一层可写容器层在镜像基础上,所有的修改仅发生在这一层
- 执行docker build时,指定目录为build context,上下文会发送给Docker daemon
- .dockerignore 类似.gitignore
- 多阶段构建有助于减小镜像大小和层数,不需要的包不装,构建下一层前清除当前层不需要的东西
- 应用解耦: 尽可能一个容器一个进程,通过容器间通信配置可以解决依赖
- 减少层次: RUN,COPY,ADD创建实际的层,其他指令创建临时中间层
- 构建时指令顺序执行时会检查现有镜像的缓存
- 从已经缓存的基础镜像开始,下一个指令检查基础镜像的子镜像是否有匹配
- ADD,COPY是否使用缓存会通过比较文件指纹
创建基础镜像
多阶段构建
- 同一个Dockerfile
- 多个FROM,通过–from,下一阶段可以通过引用上一阶段构建好的镜像临时容器中获取需要的文件或程序
- 可以只构建指定阶段,也可以直接引用其他镜像
网络配置
网络驱动
- 可插拔
- bridge: 独立容器通信使用,适用于同一个宿主不同容器通信
- host: 容器直接使用宿主机的网络,swarm不可用。适用于docker不隔离而容器隔离
- overlay: 连接多个Docker daemon,用于支持集群swarm services。适用于容器运行在不同宿主,或多服务间通信
- macvlan: 分配MAC地址给容器,Docker daemon通过MAC地址路由。适用于从虚拟机迁移
- none: 禁用网络
- docker network ls 可以看见已有的网络及对应的驱动类型
bridge
- 分为默认和自定义
- 使用默认时,每个容器都需要打开指定端口。使用自定义网络时,处于同一网络的容器间端口是相互暴露,而只需要供外部访问的容器打开端口即可
- 使用自定义网络,容器可以加入和移出自定义网络
- 自定义网络有单独的配置文件
- 默认网络使用–link参数可以使容器间共享环境变量,自定义网络则需要考虑从共享数据卷,docker-compose,swarm services实现环境变量共享
- 自定义网络通过-p参数指定对其他网络的暴露端口
- 容器创建时可以指定加入的网络,已经运行的可以通过docker network connect加入
- docker attach 将连接宿主与容器的标准输入输出错误流
- 实践可以参考https://docs.docker.com/network/network-tutorial-standalone/
host
- 容器与宿主没有隔离,容器的端口直接使用宿主的端口
- 可以用于swarm services,如果容器绑定指定端口,则一个节点只能启动一个容器
overlay
- 用于创建分布式网络连接多个docker daemon,路由转发数据包
- docker swarm init 初始化时,docker network ls 可以查看到,默认网络ingress
- 同一个docker daemon下的container通信用默认的bridge网络docker_gwbridge
- 每一个docker daemon 需要开放系列端口:TCP2377用于集群的管理通信,TCP7946和UDP7946用于节点间通信,UDP4789用于overlay网络转发
- 自定义overlay网络,可以–opt encrypted加密节点数据传输
- 可以自定义默认ingress的配置,需要通过移除和重建的方式
- 修改docker_gwbridge配置时,需要停止docker,也是通过移除和重建的方式
场景
- 外部访问容器:通过端口映射将容器端口暴露
- 容器互联:基础服务容器不对外暴露端口,只供应用层服务容器访问,则传统方式通过–link手动互联,更好的方式是自定义网络,将容器加入同一个网络。Docker通过提供环境变量和更新host文件提供基础服务容器连接信息。
存储
持久化存储
volumes
- 数据卷作为宿主文件系统的一部分,由docker管理,非docker进程无法修改,官方推荐最佳数据持久化方式
- 可以同时挂载到多个容器,可以同时读写
- 可以存储数据到云端
- 可以方便的进行数据迁移
bind mounts
- 可以在宿主任何位置存储,非docker进程可以修改
- 直接将宿主的文件或文件夹挂载到容器,不利于管理
- 使用场景:共享配置文件如dns,在开发环境容器进行打包时可以生成包到指定路径,
tmpfs mounts
- 存在宿主内存,而不是容器可写存储层
- 主要基于性能考虑,如大量数据写时
总结
- 文档帮助我们快速入门及对docker的整体有个大体了解。通过官网的实践例子,我们对docker不再陌生,剩下的则是实际开发生产解决具体问题,以下的几个问题还需要在实际应用过程中进行总结。
- 如何更快构建更小的镜像,需要了解Dockerfile各个指令,多阶段构建,构建缓存使用等
- 如何利用集群使服务高效便捷地扩展,如何编排服务组,分配资源等
- 如何利用网络通信,使不同容器,服务,节点间进行解耦合作
- 如何解决不同容器,服务,节点间数据持久化,共享
- 实际生产监控与报警