Docker 基础入门

Shawn Deng,docker

docker 命令结构

docker + 管理的对象(比如容器,镜像) + 具体操作(比如创建,启动,停止,删除)

在命令使用的过程中下面所述的简化版本是docker 早期的命令,见识使用完整命令

如果是 win 环境,建议在 powershell下使用docker 命令

小技巧

container 批量清理

docker system prune -f # 批量清理停止的容器
docker image prune -a # 清理所有镜像

container 选择

当我需要指定 container 并使用他的id 的时候,我们可以简写 id

PS C:\Users\colyerdeng> docker container run -d -p 80:80 nginx
1334e0cd0e9e26d941f2d4647e9c03134fe3a6dfba248347f8db4de3419ae3aa
 
PS C:\Users\colyerdeng> docker container ls
CONTAINER ID   IMAGE     COMMAND                   CREATED          STATUS          PORTS                NAMES
1334e0cd0e9e   nginx     "/docker-entrypoint.…"   10 seconds ago   Up 10 seconds   0.0.0.0:80->80/tcp   sweet_vaughan
PS C:\Users\colyerdeng> docker container stop 133
133
PS C:\Users\colyerdeng>

docker container run 背后的流程

docker container run -d --publish 80:80 --name webhost nginx

临时docker 执行后的清理

不加上 --rm 这个命令会残留container

docker container run --rm -it ubuntu echo "hello docker" 

docker 缓存

在docerfile 中,当我某一行发生变化,这一行之后的所有命令都不会使用cache

dockerignore

docker image build -t demo:0.1.0 . # 这里的 . 指定的是当前目录

我们所说的context 指的是上面命令中指定的目录,这个目录中可能会有很多我们不想构建进docker image 的东西。这个时候我们就可以使用 .dockerignore.gitignore 本质上一样。

镜像的多阶段构建

当我们构建的产物不依赖基础镜像的时候,我们就可以使用多阶段构建来减小整个镜像的体积

FROM gcc:9.4 AS builder # 起了一个别名 这个镜像 1.14G
 
COPY hello.c /src/hello.c
 
WORKDIR /src
 
RUN gcc --static -o hello hello.c
# 上面的第一阶段只负责build
 
 
FROM alpine:3.13.5
 
COPY --from=builder /src/hello /src/hello # 这里引用了第一阶段的产物
 
ENTRYPOINT [ "/src/hello" ]
 
CMD []

尽量使用非Root 用户

内容参考 (opens in a new tab)

环境清理

强制删除所有容器,系统清理和volume清理、还有没有使用的网络

$ docker rm -f $(docker container ps -aq)
$ docker system prune -f
$ docker volume prune -f

Contaniner

container 模式

模式范例说明
attachdocker container run -p 80:80 nginx- 透过这种方式创建容器的话,容器在前台执行
- 容器的输入输出结果会反映到本地端,本地端的输入输出也会反映到容器,例如能在终端机看到网页浏览器的 log,ctrl + c 会让容器停止执行
- 一般情况不推荐使用
detachdocker container run -d -p 80:80 nginx容器会在后台执行

container 常用命令

命令简化命令说明
docker container run image namedocker run image name容器的创建
docker container lsdocker ps容器的列出(up)
docker container ls -adocker ps -a容器的列出(up和exit)
docker container stop name or IDdocker stop container name or ID容器的停止
docker container rm name or IDdocker rm container name or ID容器的删除
docker container rm name or ID -fdocker rm container name or ID -f容器的删除(强制)
docker container ls -aq列楚所有容器的 id
docker container rm $(docker container ls -aq)强行删除所有容器
docker container rm $(docker container ls -aq) -f强行删除所有容器(包含正在运行的)
docker container run -it image name command sh运行容器,并以交互式的模式进入容器,使用 exit 的时候,会退出shell 并停止容器
docker container exec -it name or ID sh进入到已经正在运行的容器的交互模式,使用 exit 的时候,会退出shell 不会停止容器
docker container top name or ID查看某个container 进程,这些进程其实是在宿主机上的,在 linux 下可以安装 pstree 查看进程依赖关系

Image

常见的docker 镜像网站

image 常用命令

镜像的获取
命令简化命令说明
docker image pull image namedocker pull image name拉取镜像
docker image pull image name:tagdocker pull image name:tag拉取指定版本镜像
镜像的查看
命令简化命令说明
docker image ls查看本地镜像
docker image inspect name or ID显示指定镜像的详细信息
docker image history name or ID查看镜像的分层
镜像的删除
命令简化命令说明
docker image rm name or ID删除镜像(如果有容器使用到这个镜像,就无法删除,必须删除对应的 container 才能删除)
镜像的导入导出
命令简化命令说明
docker image saveimage name:tag -o filename.image导出镜像成文件
docker image load -i filepath导入镜像文件
镜像的构建( dockerfile )
命令简化命令说明
docker image build -t image name:tag filepath floder根据docker file 构建镜像(当不指定tag的时候就是latest )
docker image build -f docker file path -t image name:tag filepath floder指定docker file 构建
镜像的发布
PS C:\Users\colyerdeng> docker login
 
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: shawndengdocker
Password:
Login Succeeded
 
Logging in with your password grants your terminal complete access to your account.
For better security, log in with a limited-privilege personal access token. Learn more at https://docs.docker.com/go/access-tokens/
 
PS C:\Users\colyerdeng> docker image push [image name]

当我们在发布镜像的时候需要使用:用户名/镜像:tag 的模式

命令简化命令说明
docker image tag image name 用户名/镜像:tag修改已经构建好的镜像的名字和tag
docker login登录docker
docker image push 用户名/镜像:tag推送本地build好的image

镜像的 commit 构建

命令简化命令说明
docker container commit name or ID 用户名/镜像:tag这个命令将会在已有的容器上构建一下显得image,保存在本地(因为可能出现直接在docker container 交互运行环境中修改了 docker container 的内容的情况)

DockerFile

基础镜像选择的时候基本按照如下的原则:

一个简单的build:

# index.html
<h1>Hello Docker</h1>
 
# docker file
FROM nginx:1.21.0-alpine
 
ADD index.html /usr/share/nginx/html/index.html
 
# cmd
docker image build -t mynginx:0.1.0 . # 构建镜像
 

dockerfile 语法

RUN

RUN 主要用于在Image里执行指令,比如安装软件,下载文件等。

# cmd:
$ apt-get update
$ apt-get install wget
$ wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
$ tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
$ mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
$ rm -rf ipinfo_2.0.1_linux_amd64.tar.gz

等价的dockerfile:

# docker file
FROM ubuntu:20.04
RUN apt-get update
RUN apt-get install -y wget
RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
RUN tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
RUN mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
RUN rm -rf ipinfo_2.0.1_linux_amd64.tar.gz

在docker file 中每执行一次 run 就会产生一层 image layer 会导致镜像的臃肿

# 改进版本的docker file 
FROM ubuntu:20.04
RUN apt-get update && \
    apt-get install -y wget && \
    wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
    tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
    mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
    rm -rf ipinfo_2.0.1_linux_amd64.tar.gz

COPY | ADD

# COPY Example
FROM python:3.9.5-alpine3.13
COPY hello.py /app/hello.py # 比如把本地的 hello.py 复制到 /app 目录下。 /app这个folder不存在,则会自动创建
 
# ADD Example
FROM python:3.9.5-alpine3.13
ADD hello.tar.gz /app/

WORKDIR

切换当前的工作目录

FROM python:3.9.5-alpine3.13
WORKDIR /app # 切换工作目录,当目录不存在的时候会自动创建
COPY hello.py /app/hello.py 

ARG | ENV

ARGENV 是经常容易被混淆的两个Dockerfile的语法,都可以用来设置一个“变量”。 但实际上两者有很多的不同。

当我们需要引用关键字的时候使用: ${KEY WORDS}

ARG
FROM ubuntu:20.04
ARG VERSION=2.0.1
RUN apt-get update && \
    apt-get install -y wget && \
    wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
    tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
    mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
    rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
ENV
FROM ubuntu:20.04
ENV VERSION=2.0.1
RUN apt-get update && \
    apt-get install -y wget && \
    wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
    tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
    mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
    rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
区别

ARG 更关注于构建过程,但是 ENV 是可以在全部情况下使用的

image-20240222174927081

CMD

CMD可以用来设置容器启动时默认会执行的命令。

FROM ubuntu:20.04
RUN apt-get update && \
    apt-get install -y wget && \
    wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
    tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
    mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
    rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
CMD ["ipinfo"] # 这里执行了 ipinfo

ENTRYPOINT

ENTRYPOINT 也可以设置容器启动时要执行的命令,但是和CMD是有区别的。

# docker file 1 demo-cmd
FROM ubuntu:20.04
CMD ["echo", "hello docker"] # 这个可以被覆盖
 
# echo
$ docker container run -it --rm demo-cmd
hello docker
$ docker container run -it --rm demo-cmd echo "hello world"
hello world
 
 
# docker file 2 demo-entrypoint
FROM ubuntu:20.04
ENTRYPOINT ["echo", "hello docker"] # 这个是必定会被执行
 
# echo
$ docker container run -it --rm demo-entrypoint
hello docker
$ docker container run -it --rm demo-entrypoint echo "hello world"
hello docker echo hello world
 

EXPOSE

暴露docker contianer 内部的端口给宿主机(更多的功能是解释说明,不适用也可以暴露出去)

from flask import Flask
 
app = Flask(__name__)
 
 
@app.route('/')
def hello_world():
    return 'Hello, World!'
FROM python:3.9.5-slim
 
COPY app.py /src/app.py
 
RUN pip install flask
 
WORKDIR /src
ENV FLASK_APP=app.py
 
EXPOSE 5000
 
CMD ["flask", "run", "-h", "0.0.0.0"]

Docker Volume

image-20240223104614071

VOLUME

当我们需要持久化docker container 中的数据,需要使用VOLUME 来声明持久化的文件或者文件夹

FROM alpine:latest
RUN apk update
RUN apk --no-cache add curl
ENV SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/v0.1.12/supercronic-linux-amd64 \
    SUPERCRONIC=supercronic-linux-amd64 \
    SUPERCRONIC_SHA1SUM=048b95b48b708983effb2e5c935a1ef8483d9e3e
RUN curl -fsSLO "$SUPERCRONIC_URL" \
    && echo "${SUPERCRONIC_SHA1SUM}  ${SUPERCRONIC}" | sha1sum -c - \
    && chmod +x "$SUPERCRONIC" \
    && mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \
    && ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic
COPY my-cron /app/my-cron
 
VOLUME [ "/app/" ]
 
# RUN cron job
CMD ["/usr/local/bin/supercronic", "/app/my-cron"]

启动命名

docker container run -d -v cron-data:/app my-cron # 这里指定了映射关系
命令说明
docker volume ls列出所有的持久化数据
docker volume inspect Volume name查看指定 Volume 的信息
Container 被删除后,持久化的数据还是会存在,下次我们再次指定这个 Volume name 就可以 mount 到对应的Container

Bind mount

链接宿主机的本地路径

将上面的mount 的名字换成路径即可

多机器之间的容器数据共享 (opens in a new tab)

这个依赖docker 的插件

Docker 网络

常用的网络命令

命令例子说明
ifconfiglinux 查看网络配置
ipconfigwin 查看网络配置
pingping 192.168.2.1查看网络联通性
telnettelnet www.baidu.com (opens in a new tab) 80查看端口的可达性

Bridge 网络

命令例子说明
docker network create -d bridge bridge name创建自己的一个bridge
docker network create -d bridge --gateway 网关地址 --subnet 网段地址 bridge namedocker network create -d bridge --gateway 172.200.0.1 --subnet 172.200.0.0/16 demo创建一个bridge,并指定网关和网段
docker network connect bridge name container name将container 连接上一个bridge
docker network disconnect bridge name container name将container 取消连接 bridge
docker network inspect bridge name查看bridge 的信息
docker container run -d --network network name image name启动容器并指定网络模式

当我们使用交互模式去ping 在我们手动创建下的 bridge 的 container 的时候,可以 ping container 的名字,但是默认的 bridge 不可以 ,因为默认的bridge 不提供 dns 解析

Host

与宿主机共享网络(容器和宿主机的网络配置一摸一样)

网络的命名空间

Docker Compose

Docker Compose file 语法 (opens in a new tab)

默认的docker compose file 文件名是 docker-compose.yml

version: "3.8"
 
services: # 容器
  servicename: # 服务名字,这个名字也是内部 bridge网络可以使用的 DNS name
  	container_name: # 手动指定创建的container的名字 不建议用
    image: # 镜像的名字
    command: # 可选,如果设置,则会覆盖默认镜像里的 CMD命令
    environment: # 可选,相当于 docker run里的 --env
    volumes: # 可选,相当于docker run里的 -v
    networks: # 可选,相当于 docker run里的 --network
    ports: # 可选,相当于 docker run里的 -p
  servicename2:
 
volumes: # 可选,相当于 docker volume create
 
networks: # 可选,相当于 docker network create

一个实际的例子:

version: "3.8"
 
services:
  flask-demo:
    image: flask-demo:latest
    environment:
      - REDIS_HOST=redis-server
    networks:
      - demo-network
    ports:
      - 8080:5000
 
  redis-server:
    image: redis:latest
    networks:
     - demo-network
 
networks:
  demo-network:

Docker compose 的使用

命令例子说明
docker-compose up使用当前目录下的 compose file 来启动
docker-compose up -d后台运行当前目录下的 compose file
docker-compose -p project name up -d设定当前启动的compose file的项目名字,不设定就是当前文件夹名字(最好还是在compose file 中通过 container_name 来设定),不然使用基本命令的时候始终需要加上 -p
docker-compose log查看当前目录下的 compose file 的 docker compose 的log
docker-compose log -f持续的动态查看
docker-compose up -f docker file path使用指定路径的 yml 来启动 docker-compose
docker-compose stop停止compose
docker-compose rm删除停止的compose container
docke-compose build通过 compose file 构建
docker-compose pull拉取镜像
docker-compose up -d --build服务热更新
docker-compose up -d --remove-orphans当compose file 中的容器删除时候,可以通过它来同步删除服务中对应的容器
docker-compose restart重启服务

在开发过程中,我们希望通过compose file 也能够完成构建 :

version: "3.8"
 
services:
  flask-demo:
    image: flask-demo:latest
    build: # docker file 所在文件夹路径, 构建的时候会自动以image 的信息命名,build 有很多参数
		   # https://docs.docker.com/compose/compose-file/build/
    environment:
      - REDIS_HOST=redis-server
    networks:
      - demo-network
    ports:
      - 8080:5000
 
  redis-server:
    image: redis:latest
    networks:
     - demo-network
 
networks:
  demo-network:
docker-compose build

docker compose 服务热更新

在开发过程中,代码可能时常发生变更,所以我们需要在docker-compose up 的过程中自动构建

docker-compose up -d --build

会执行如下过程:

当 compose file 中的容器进行删除的时候,需要使用下面的命令来同步服务 docker-compose up -d --remove-orphans

docker compose 水平扩展

水平拓展指的是快速增加 services 的数量

命令例子说明
docker-compose up -d --scale container name=numdocker-compose up -d --build --scale flask=3启动compose file ,并将对应的容器增加对应的数量

示例docker file

version: "3.8"
 
services:
  flask:
    build:
      context: ./flask
      dockerfile: Dockerfile
    image: flask-demo:latest
    environment:
      - REDIS_HOST=redis-server
 
  redis-server:
    image: redis:latest
 
  client:
    image: xiaopeng163/net-box:latest
    command: sh -c "while true; do sleep 3600; done;"
 
docker-compose up -d --build --scale flask=3 # 构建并将 flask 这个servies 增加至 3 个,访问的时候相当于做了一个负载均衡
 
$ docker-compose ps
                Name                              Command               State    Ports
----------------------------------------------------------------------------------------
compose-scale-example_client_1         sh -c while true; do sleep ...   Up
compose-scale-example_flask_1          flask run -h 0.0.0.0             Up      5000/tcp
compose-scale-example_flask_2          flask run -h 0.0.0.0             Up      5000/tcp
compose-scale-example_flask_3          flask run -h 0.0.0.0             Up      5000/tcp
compose-scale-example_redis-server_1   docker-entrypoint.sh redis ...   Up      6379/tcp

再加上一个 ngnix 就是一个典型的负载均衡

docker compose 环境变量

docker-compose 中使用 environment 来声明环境变量

命令例子说明
docker-compose config查看当前目录下的 compose file 的设置(方便预览env 的传递是否正确)
docker-compose --env-file env file path configdocker-compose --env-file .\myenv config查看使用.\myenv file中的值的config
docker-compose --env-file env file path up -ddocker-compose --env-file .\myenv up -d使用.\myenv 的值启动服务

示例:

version: "3.8"
 
services:
  flask:
    build:
      context: ./flask
      dockerfile: Dockerfile
    image: flask-demo:latest
    environment:
      - REDIS_HOST=redis-server
      - REDIS_PASS=${REDIS_PASSWORD} # 这里的是表明使用 外部的 .env 文件中的值
    networks:
      - backend
      - frontend
 
  redis-server:
    image: redis:latest
    command: redis-server --requirepass ${REDIS_PASSWORD}
    networks:
      - backend
 
  nginx:
    image: nginx:stable-alpine
    ports:
      - 8000:80
    depends_on:
      - flask
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./var/log/nginx:/var/log/nginx
    networks:
      - frontend
 
networks:
  backend:
  frontend:
 

docker 健康检查

dockerfile

当我们在 compose file 或者dockerfile 中定义了 HEALTHCHECK字段,docker会按照我们的定义去检查容器的状态

当我们使用 docker container inspect container_name or ID ,可以看到健康检查的日志

docker 的单元测试

具体的语法可以参考:docker healthcheck (opens in a new tab)

FROM python:3.9.5-slim
 
RUN pip install flask redis && \
    apt-get update && \
    apt-get install -y curl && \
    groupadd -r flask && useradd -r -g flask flask && \
    mkdir /src && \
    chown -R flask:flask /src
 
USER flask
 
COPY app.py /src/app.py
 
WORKDIR /src
 
ENV FLASK_APP=app.py REDIS_HOST=redis
 
EXPOSE 5000
 
HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost:5000/ || exit 1
    # 每隔30s 检查一下 这个网址是否能请求成功
 
CMD ["flask", "run", "-h", "0.0.0.0"]

docker compose file

version: "3.8"
 
services:
  flask:
    build:
      context: ./flask
      dockerfile: Dockerfile
    image: flask-demo:latest
    environment:
      - REDIS_HOST=redis-server
      - REDIS_PASS=${REDIS_PASSWORD}
    healthcheck:	# 健康检查的条件
      test: ["CMD", "curl", "-f", "http://localhost:5000"] # 请求这个对应的网站是否能成功
      interval: 30s	# 每三十秒 检查一次
      timeout: 3s # 请求过程的超时是 3s
      retries: 3  # 时候健康检查是 3 次
      start_period: 40s
    depends_on:
      - redis-server
    networks:
      - backend
      - frontend
 
  redis-server:
    image: redis:latest
    command: redis-server --requirepass ${REDIS_PASSWORD}
    networks:
      - backend
 
  nginx:
    image: nginx:stable-alpine
    ports:
      - 8000:80
    depends_on: # 启动顺序
      flask: # 依赖flask 启动完成 这个容器才会启动
        condition: service_healthy # 启动条件,当只有flask 启动检查通过的时候,这个容器才能启动
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./var/log/nginx:/var/log/nginx
    networks:
      - frontend
 
networks:
  backend:
  frontend:
 

Docker Swarm(容器编排)

初始化Swarm

# 查看 swarm 是否激活
docker info
 
Client:
 ...
 
Server:
 ...
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: inactive # 这里表示没有激活
 Runtimes: io.containerd.runc.v2 runc
 ...
docker swarm init # 激活 swarm
命令例子说明
docker swarm init激活swarm
docker node ls查看节点列表
docker swarm leave让当前的节点离开集群(单节点的时候会报错)
docker swarm leave --force强制让当前节点离开集群(单节点的的时候会用这个)

docker swarm init 背后发生了什么:主要是PKI和安全相关的自动化

创建Raft数据库用于存储证书,配置,密码等数据

RAFT相关资料

看动画学会 Raft 算法

https://mp.weixin.qq.com/s/p8qBcIhM04REuQ-uG4gnbw (opens in a new tab)

docker swarm 部署app

命令例子说明
docker service create OPTIONS IMAGE [COMMAND] ARG...docker service create nginx:latest创建一个容器
docker service ls查看服务列表
docker service ps service ID or namedocker service ps don查看service 状态

docker 的多架构支持

在本地环境构建多架构image

命令例子说明
docker buildx build --push --platform platform type -t image name:tagplatform type 使用逗号分隔,这个命令表示构建多个架构的image 并推送到docker hub
docker buildx create --name name --use创建buildx 的构建环境

删除build 环境的方法:

© Shawn Deng.RSS