Docker
对于一名前端工程师来说,掌握 Docker 的一些基本用法也是很有必要的
安装
从 Docker 官方文档中找到安装教程
安装好桌面版本启动之后,将会自动注册 docker 命令
编写 Dockerfile
比如当前文档的部署例子,就可以使用如下的 Dockerfile 配置
# 拉取 nodejs 特定版本的镜像
FROM node:16-buster-slin as builder
# 拷贝当前目录到 /var/web/
COPY . /var/web/
# 进入 /var/web/ 并执行项目的打包,得到 dist
RUN set -x \
&& cd /var/web/ \
&& npm install -g pnpm \
&& pnpm docs:build
# 拉取nginx特定版本的镜像
FROM ngin:1.23.1-alpine as prod
# 容器内开放 80 端口
EXPOSE 80
# 将 dist 拷贝到 nginx 的默认静态目录下
COPY --from=0 /var/web/dist /usr/share/nginx/html
# 启动 nginx,不以守护进程模式运行
CMD ["nginx", "-g", "daemon off;" ]构建镜像
在 Dockerfile 所在目录执行如下命令
$ docker build -t fe-book .-t 的意思是 tag,相当于给镜像取了一个名字,. 代表当前目录
然后会找到当前目录的 Dockerfile,然后执行对应的构建流程
构建完成之后,可以通过如下的命令查看镜像:
$ docker image ls # or docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
fe-book latest 5e4ceef6c856 51 minutes ago 36.8MB构建 Container
有了镜像,我们就可以构建一个容器了
$ docker run -d -p 3000:80 fe-book
e96e1e0d22b45e80016d413fbbbe8a16bedd781e5aee50f99fa1f8171548b5d6-d 的意思是保持后台运行
-p 3000:80 的意思是把容器内的 80 端口映射到为本机的 3000 端口,还可以通过指定 -P,将容器端口映射为本机随机端口
fe-book 就是我们已经构建好的镜像的 tag
然后就可以通过 http://localhost:3000 访问了
构建完成之后,可以通过如下的命令查看容器:
$ docker container ls # or docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e96e1e0d22b4 fe-book "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:3000->80/tcp book停止和删除
$ docker stop e96e1 # 停止以 e96e1 开头的 ID 的容器
$ docker start e96e1 # 启动以 e96e1 开头的 ID 的容器
$ docker rm e96e1 # 删除以 e96e1 开头的 ID 的容器,必须先停止再删除
$ docker rmi fe-book # 删除 tag 为 fe-book 的镜像减小体积
通常,会设置 .dockerignore 来忽略某些文件,减少构建的时间,例如对于前端项目,可以忽略 node_modules
node_modules/Docker 是如何工作的
Docker 的核心原理是利用了 Linux 内核的 namespace 以及 cgroup 特性,其中 namespace 进行资源隔离,cgroup 进行资源分配。
Linux 内核中共有 6 中 namespace,分别对应如下
| Namespace | 系统调用函数 | 隔离内容 |
|---|---|---|
| UTS | CLONE_NEWUTS | 主机与域名 |
| IPC | CLONE_NEWIPC | 信号量、消息队列和共享内存 |
| PID | CLONE_NEWPID | 进程编号 |
| Network | CLONE_NEWNET | 网络设备、网络栈、端口等 |
| Mount | CLONE_NEWNS | 挂载点(文件系统) |
| User | CLONE_NEWUSER | 用户和用户组 |
Linux 在内核层支持了在系统调用中隔离 namespace,通过给进程分配单独的 namespace,从而让其拥有独立的主机名、IPC、PID、IP、根文件系统、用户组等信息,就像在一个独占系统中。
虽然进行了资源的隔离,但是内核还是同一个,所以这也是比传统虚拟机轻的原因
只进行资源隔离是不够的,要想保证真正的故障隔离,还需要针对 CPU、GPU、内存等进行限制,因为如何一个程序出现死循环导致内存泄露也会导致别的程序无法运行。资源配额是使用 cgroup 来完成的
cgourp 可以限制容器对于资源的占用情况,目前 docker 几乎支持了所有的 cgroup 资源,包括 网络、cpu、内存在内的资源使用,有兴趣可以查看这篇文章
Docker 网络
上面我们看到使用了 docker run -p 3000:80 的命令,其中就涉及到了 Docker 的网络部分,我们在部署 Docker 应用,需要对网络有个基本的了解
Docker 提供了四种网络模式,可以通过 --net 来指定,分别是 Host、Container、None、Bridge
Host
docker run --net host fe-bookHost 模式下不会为容器创建 network namepsace,容器内部直接使用宿主机网卡,端口直接绑定在宿主机网卡上,不用经过 NAT 转换,效率更高
Container
和指定的 Container 共享 network namespace
docker run --net container:container_name_or_id fe-bookNone
指定 None 模式的容器,内部不会被分配网卡设备,仅有内部 lo 网络
docker run --net none fe-bookBridge
docker run --net -p 3000:80 bridge fe-book该模式为默认的模式,容器启动的时候,会被分配一个单独的 network namespace,同时,Docker 在安装/初始化的时候,会创建一个名为 docker0 的网桥,改网桥也被称为默认网关
当执行 docker run -p 3000:80 fe-book 时,Docker 会在宿主机上创建下面一条 iptable 转发规则。
修改好目的地址之后流量会从本机的默认网卡经过 docker0 转发到对应的容器,这样当外部请求 3000 端口的时候,内部会将流量转发给容器内部,从而实现服务的暴露
而来自内部的请求,也会进行源地址转换(SNAT),服务器上收到的将是主机网卡的 ip
Bridge 模式由于多了一层 NAT 映射,所以效率会比 HOST 模式低一些,但是能够很好的隔离外部环境,让容器独享 ip 且具有完整的端口空间
docker-compose
在实际的使用中,我们经常会使用 docker-compose 命令来启动多容器的应用实例
$ docker-compose up -d这需要搭配一个 docker-compose.yml 文件,直接方便快速地启动多个容器。
例如下面的配置快速启动一个 immich-app/immich 应用,
#
# WARNING: Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.
#
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
# extends:
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
volumes:
# Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- 2283:3001
depends_on:
- redis
- database
restart: always
immich-machine-learning:
container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
# Example tag: ${IMMICH_VERSION:-release}-cuda
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
# extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
# file: hwaccel.ml.yml
# service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
volumes:
- model-cache:/cache
env_file:
- .env
restart: always
redis:
container_name: immich_redis
image: docker.io/redis:6.2-alpine@sha256:e3b17ba9479deec4b7d1eeec1548a253acc5374d68d3b27937fcfe4df8d18c7e
healthcheck:
test: redis-cli ping || exit 1
restart: always
database:
container_name: immich_postgres
image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
# Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
restart: always
volumes:
model-cache:常用命令
查看容器占用的端口号
$ docker port CONTAINER_ID_or_NAME进入容器内
$ docker exec -it CONTAINER_ID sh重新构建并启动
$ docker-compose up --build后台启动
$ docker-compose up -d更新镜像并重新启动
$ docker-compose pull
$ docker-compose up -d