跳到主要内容

1.1-docker

Create by fall on 09 May 2023 Recently revised in 18 Jul 2023

Docker

目前,我们有三个不同的软件运行方式:

物理机、虚拟机、容器

  • 物理机时代:多个应用会跑在一台机器上(更换环境,需要更换对应的机器)
  • 虚拟机时代:一台物理机通过虚拟机运行多个环境,环境分别运行多个程序(VMware,在 mac 系统上我装一个 Windows 系统,装一个 CuteFish)
  • 容器化时代:一台物理机安装多个容器实例,多个实例运行多个程序,比虚拟机更小(docker,一个容器,使用一个进程,作为一个模拟系统)

docker 提供了容器化部署的能力。

使用 docker 进行开发,一般是以下的几个过程

  • Dockerfile 目录内开发
  • 开发完成后创建镜像(覆盖镜像)
  • 最后创建容器,运行容器。

镜像

镜像(Image):一个系统的一瞬间进行保存,可以不断地从该点创建分支进行运行(要修改只能创建新的镜像)。

Docker 把应用和依赖放在镜像文件里面,只有通过镜像才能生成容器。镜像生成的容器都是独立的,即使使用同一个镜像创建容器,容器里面的内容都是互独立的。所以我们一般也不在容器内进行修改,因为这些修改无法长期存储。

image 是二进制文件。实际开发中,一个 image 文件往往通过继承另一个 image 文件,加上一些个性化设置而生成。你可以在 Ubuntu 的 image 基础上,往里面加入 Apache 服务器,形成你的 image。

image 文件是通用的,一台机器的 image 文件拷贝到另一台机器,照样可以使用。一般来说,使用别人制作好的 image 文件,而不是自己制作,以节省时间。即使要制作,也应该基于别人的 image 文件进行加工,创建镜像,而不是从零开始。

容器

容器:从某一个镜像开始运行的点,并且容器之间互相独立。

Linux 有一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。

由于容器是进程级别的,相比虚拟机有很多优势。

  • 启动快:容器里面的应用,直接就是底层系统的一个进程,而不是虚拟机内部的进程。所以,启动容器相当于启动本机的一个进程,而不是启动一个操作系统,速度就快很多。
  • 资源占用少:容器只占用需要的资源,不占用那些没有用到的资源;虚拟机由于是完整的操作系统,不可避免要占用所有资源。另外,多个容器可以共享资源,虚拟机都是独享资源。
  • 体积小:容器只要包含用到的组件即可,而虚拟机是整个操作系统的打包,所以容器文件比虚拟机文件要小很多。

总之,容器有点像轻量级的虚拟机,能够提供虚拟化的环境,但是成本开销小得多。

默认情况下,容器不会知道同一个机器上,任何其他线程或者容器的任何信息

用途

**Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。**它是目前最流行的 Linux 容器解决方案。

Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。

Docker 的主要用途,目前有三大类。

**(1)提供一次性的环境。**比如,本地测试他人的软件、持续集成的时候提供单元测试和构建的环境。

**(2)提供弹性的云服务。**因为 Docker 容器可以随开随关,很适合动态扩容和缩容。

**(3)组建微服务架构。**通过多个容器,一台机器可以跑多个服务,因此在本机就可以模拟出微服务架构。

实际使用中,业务发展初期只有几个微服务,这时用 Docker 就足够了,但随着业务规模逐渐扩大,容器越来越多,运维人员的工作越来越复杂,这个时候就需要编排系统解救 opers

命令

镜像

部分镜像可能无法通过 docker 进行拉取,可以在这个镜像地址搜索并获取:https://quay.io/search

docker images 查看所有 docker 镜像(等价于 docker image ls

docker pull hello-world 从远程拉取 hello-world 镜像

docker rmi hello-world 删除 hello-world 镜像

docker create <image_name> 创建一个新的镜像

docker run <hello-world> 启动镜像(创建容器)(如果没有,会从网络上拉取 docker pull),额外参数如下

# 以交互模式运行 ubuntu
docker container run -it ubuntu bash

# 多个配置可以分别书写
docker run --name example-name -d --env DB_NAME=gitlabhq_production --env DB_USER=gitlab --env DB_PASS=admin_99565 --env --publish 7022:22 DB_EXTENSION=pg_trgm,btree_gist --volume /srv/docker/gitlab/postgresql:/var/lib/postgresql example-image:12.22.12
  • -i 以交互模式运行镜像
  • -e & --env 配置环境变量(只推荐在开发环境中使用环境变量配置连接端口
  • -d & --detach 创建一个守护式容器在后台运行(容器创建后自动在后台运行)
  • -t 创建启动后会进入其命令行。(加入 -it 后,会创建立即登录进入,分配一个伪终端)
  • --name 为创建的容器命名
  • -v & -volume 表示目录映射关系
  • -w & --workdir 容器的工作目录(如果路径不存在,会在容器内创建目录,并且会默认进入该目录)
  • --mount 设置 volume 映射
  • -p & --publish 表示端口映射(-p 2022:80 将容器的 80 端口映射到本地的 2022 端口上)
    • 没有端口映射,你不能通过端口进入应用(Without the port mapping, you wouldn’t be able to access the application from the host.)
  • -network=host 让容器和主机共享一个网络空间
  • --shm-size 共享内存设定值
    • 共享内存:允许两个不相关的进程访问同一个逻辑内存,两个正在运行的进程之间共享和传递数据的一种有效方式
  • --hostname 主机名,当配置域名的时候需要使用

拉取后的镜像如何使用,请参考对应镜像的文档,或者在该网站(https://hub-stage.docker.com)进行搜索对应的镜像,查看使用方式

创建镜像

创建镜像时必须要用到 Dockerfile,会使用该 Dockerfile 文件的配置生成二进制的 image 文件。

创建 docker 镜像,大致需要三个步骤

  • 创建 Dockerfile 文件
  • 编写文件内容
  • 使用 docker build -t getting-started . 命令

最后的 . 符号说明可以在当前文件夹内找到 Dockerfile

docker build 命令的结构 docker build -t <image_name> <dockerfile>

  • -t-tag 的缩写,允许镜像指定后面的标签,比如 redis:6.2,通常用来区分版本
  • <image_name> 镜像名称
  • <dockerfile> dockerfile 文件,指定路径
  • --platform linux/amd64 可选参数,指定环境为 linux/amd

容器

容器相关命令

# 查看正在运行的容器
docker ps
docker ps -a # 查看所有容器
# 启动容器
docker start <container-name & id>
# 停止容器
docker stop <container-name & id>
# 强制停止容器
docker kill <container-name & id>
# 重启容器
docker restart <container-name & id>

# 从磁盘中删除容器
docker container rm <container-name & id>
# 在运行的指定容器中执行命令(需要启动容器)
docker exec -it <container-name & id> <command>
# 在运行的指定容器中,打开 shell
docker exec -it <container-name & id> sh

容器内数据的备份

我们可以利用 bind mount 的特性,创建一个主机和容器共享的目录,将内容压缩进来,然后作为备份使用

如果容器不方便停用,可以遵循以下步骤

  • 创建一个新的 ubuntu 容器
  • 为该容器创建 volume mount(需要备份的数据目录)和 bind mount(目标目录,可为空目录)
  • 将 volume mount 的文件压缩到 bind mount

volume

容器对文件进行的任何操作都将会在移除容器(container)后丢失,Docker 会隔绝 container 所有的操作。volume 能在选定容器和当前文件系统路径进行链接(映射)。

所以我们需要用 volume 将存储的数据(或者开发的内容)映射到操作系统的目录中来,如果没有 volume,修改的内容将会彻底丢失。

主要有两种 volume 类型

  • volume mount:存储你的应用数据(docker 决定存储位置,让各个容器之间共享数据)
  • bind mount:在本地和容器之间共享一个文件系统

volume mount(具名 volume)

  • 创建:docker volume create my-volume
  • 获取 volume 列表:docker volume ls
  • 获取 volume 列表:docker volume rm my-volume
# 启动镜像,并且将目录绑定到 volume 容器上
docker run -dp 127.0.0.1:3000:3000 --mount type=bind,src=/path/to/data,target=/usr/local/data <image-name>
# 如果想声明 volume,可以更改 type 为 type=volume,src=my-volume,target=/usr/local/data
# src 表示使用的 volume
# target 容器对应的路径

bind mount(绑定 volume)

# --mount 去创建 volume
docker run -it --mount "type=bind,src=$pwd,target=/src" ubuntu bash
# 将当前目录和新创建的 ubuntu 容器指定目录进行绑定。
# 绑定后,如果绑定目录存在内容,会将容器内的目录屏蔽(不会删除或者修改,但是也不能使用),使用本地文件夹代替 mount 文件夹使用

如果指定的 volume 不存在,docker 默认会创建该 volume 的内容

  • volume mount 如果没有内容,添加后默认内容将会是容器中目录内的内容
  • bind mount 绑定后,如果绑定目录存在内容,会屏蔽容器内的目录(不会删除或者修改,但是也不能使用),使用本地文件夹代替 mount 的文件夹使用。

bind mount 关联的文件系统,如果在容器内运行,就是调用容器的 api 和功能,如果在本地运行,就是调用本地的功能。推荐应用场景:

  • 在本地和容器间共享配置文件
  • 提供源码或者在环境中构建
Named volumesBind mounts
本地存放位置Docker 决定自己决定
覆盖新容器的内容

网络

容器之间,默认不会有任何沟通,需要为容器添加相同的网络,然后容器之间才能通过网络进行沟通

每个容器都有固定的 ip,为容器添加网络有两种方式

  • 方法一:通过镜像创建容器的时候
  • 方法二:向已有的容器添加网络

无论哪个方法,首先先添加网络

docker network create todo-app

方法一:

创建时链接网络

docker run -d `
--network todo-app --network-alias mysql `
-v todo-mysql-data:/var/lib/mysql `
-e MYSQL_ROOT_PASSWORD=secret `
-e MYSQL_DATABASE=todos `
mysql:8.0
# 通过 --network todo-app 指定网络
# --network-alias 为网络命名,容器内部搜索网络时,可以通过该别名搜索,并且可以将其它容器的 HOST 配置为该别名

方法二:将应用容器挂载到网络上

docker network connect <network_id or network_name> <container_id or container_name>

网络相关命令

# 列出所有网络
docker network ls
# 创建一个网络
docker network create todo-app
# 创建网络
docker network create --driver <driver-name> <bridge-name>
# 容器连接到网络
docker network connect <network_id or network_name> <container_id or container_name>
# 断开容器和网络的链接
docker network disconnect <network_name_or_id> <container_name_or_id>
# 移除网络
docker network rm <network_id or network_name>

日志

系统运行时,命令行输出的内容

# 获取容器日志 docker
docker container logs <container_id or container_name>
docker logs <container_id or container_name>
# 监控容器日志
docker container logs -f <container_id or container_name>

工程化

多容器应用

In general, each container should do one thing and do it well.

通常来讲,每个容器应该只做一件事,并且把事情做好,所以镜像需要功能相互独立。

  • 独立容器可以简化前对前端,和数据库做不同的扩展。
  • 分离的容器,能更好的隔离以及更新容器内容的版本。
  • 运行多线程,将会需要进程管理器(增加容器启动和关闭复杂性)。容器只会使用一个进程。

Dockerfile

创建镜像必须使用 Dockerfile,Dockerfile 文档会包含一个用户在命令行调用去组装 image 的所有命令

包括从哪个镜像进行构建、工作目录等一系列内容

示例:

# Dockerfile 配置文件
# 格式:命令 内容
FROM node:8.4
COPY . /app # 将 . 当前目录的内容,都拷贝到 ./app 目录下
WORKDIR /app # 将 /app 设定为工作路径
RUN npm install --registry=https://registry.npm.taobao.org # 在大包为镜像时,执行命令
EXPOSE 3000 # 暴露的端口,会在 -p 使用到

docker-compose

如果将配置全在命令行里书写,不容易保存,且一次只能生成一个容器,修改和查找上下文也不方便。

以 docker-compose 来运行命令,可以解决上面的问题。即,通过一个 docker-compose.yml 文件生成多个容器。

当我们使用 docker compose 后,一行简单的命令,就可以飞速创建容器

自动化部署

使用 docker + jenkins 能实现代码提交到 github 后自动部署环境

  • docker login 进行登录
  • docker login -u username 指定用户名称
  • docker push username/image-name 将镜像推送到 docker hub

参考文章

作者文章名称
爱笑的架构师5分钟带你快速了解Docker和k8s
wljslmzDocker小白的福音:50条Docker命令清单,干就完了!
Rain_or_ShineDocker 入门教程-阮一峰
天行无忌WEB开发人员应该知道 10 个 Docker 命令
杨高超通过 docker 搭建自用的 gitlab 服务
官方文档https://docs.docker.com/get-started/02_our_app/
佳庆docker-compose讲解与安装