Docker自学笔记


参考链接:https://dockertips.readthedocs.io/en/latest/

学习链接:https://coding.imooc.com/class/chapter/511.html

既然人家写了笔记为什么还要自己写一遍?

  1. 为了自己写一遍熟悉下
  2. 自己查询起来快一点,他的笔记是挂在github上的,点一下加载下难受
  3. 好多注释不全,久了没用到会忘记~

首发于 https://sleepymonster.cn

容器快速上手

$ docker container run -d -p 80:80 nginx # 后台运行创建一个nginx容器

$ docker run -it $(name) # 执行容器中的默认脚本

$ docker container exec -it $(id) sh # 交互式进入容器

$ docker system prune -f # 删除所有已经停止的容器

$ docker image prune -a # 删除所有没有使用的镜像

镜像的创建管理和发布

一些Image的基本使用

# 从hub上拉取
$ docker image pull nginx  # 拉取镜像
$ docker image ls # 显示本地存在的镜像
$ docker image pull nginx:1.20.0 # 拉取特定版本

# 从quay等仓库拉取
$ docker image pull quay.io/bitnami/nginx  # 从别的库拉取

# 基本操作
$ docker image inspect $(id) # 查看详细信息
$ docker image rm $(id) # 删除镜像 注意容器只有不存在了才能删除镜像

# 导入导出
$ docker image save nginx:1.20.0 -o nginx.image
$ docker image load -i ./nginx.image

初识Dockerfile

参考链接:https:#docs.docker.com/engine/reference/builder/

  • FROM 导入 RUN 运行 ADD 添加文件 CMD 执行
FROM ubuntu:21.04
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev
ADD hello.py /
CMD ["python3", "/hello.py"]
# 自己构建一个docker
$ docker image build -t hello:1.0 ./
$ docker image build -f dockerFile -t hello:1.0 ./

# 分享出去
$ docker image rm $(name):$(tag) # 删除具有相同的id的
$ dokcer image $(oldName) $(DockerId)/$(oldName):1.0 # 确保名字符合标准以及设置版本
$ docker login # 登陆自己的账号
$ docker image push $(name):$(tag) # push上去

通过commit从容器创建image

$ docker container commit $(id) $(name) # 把容器创建为镜像

# 如何解决创建了但是没有对应的脚本
$ docker container run -it ubuntu # 进入容器
# 将命令复制粘贴到shell中执行
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev
# 写入文件
echo "print("hello docker")" > hello.py
# 打包创建容器
$ docker container commit $(id) $(name)
# 没有sh的话会进入交互 可以设置执行文件
$ docker container run -it $(name) python3 /hello.py

scratch来构建一个基础镜像

Scratch是一个空的Docker镜像。

参考链接:https:#dockertips.readthedocs.io/en/latest/docker-image/scratch-image.html

FROM scratch
ADD hello /
CMD ["/hello"]
# 构建
$ docker build -t hello .
$ docker image ls
# 运行
$ docker container run -it hello

Dockerfile操作指南

镜像的选择

# 最好官方或者开源的版本
# 固定版本tag而不是每次都使用latest
# 基于alpine构建的镜像会很小
FROM nginx:1.21.0-alpine

ADD index.html /usr/share/nginx/html/index.html

基础操作

  • RUN
# ⚠️注意:每个RUN都会产生新的一层,推荐直接放到一个RUN里面
FROM ubuntu:21.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

# 改进为一层
FROM ubuntu:21.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
  • ADD || COPY

COPYADD 都可以把local的一个文件复制到镜像里,如果目标目录不存在,则会自动创建

ADD 比 COPY高级一点的地方就是,如果复制的是一个gzip等压缩文件时,ADD会帮助我们自动去解压缩文件。

FROM python:3.9.5-alpine3.13
WORKDIR /app  # 切换所属位置
COPY hello.py /app/hello.py  # 把本地的 hello.py 复制到 /app 目录下。 /app这个folder不存在,则会自动创建
ADD hello.tar.gz /app/   # 复制的是一个gzip等压缩文件
  • CMD

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

# 例如:docker run -it $(name) 默认进入到shell是因为在ubuntu的基础镜像里有定义CMD
# 如果docker container run启动容器时指定了其它命令例如:docker container exec -it $(id) sh,则CMD命令会被忽略
# 如果定义了多个CMD,只有最后一个会被执行。

$ docker container run --rm -it $(name) $(cmd) # 一次性运行命令后退出且删除 
  • ENTRYPOINT
# CMD设置的命令,可以在docker container run 时传入其它命令,覆盖掉CMD的命令,但是ENTRYPOINT所设置的命令是一定会被执行的。
# ENTRYPOINT和CMD以联合使用,ENTRYPOINT设置执行的命令,CMD传递参数
FROM ubuntu:21.04
ENTRYPOINT ["echo"]
cmd []

# docker container run -rm -it $(name) test
# 会先执行eaho test作为参数传入输出出来

构建参数和环境变量

ARGENV 几乎相同但是

ARG 可以在镜像build的时候动态修改value, 通过 --build-arg(活)

ENV 设置的变量可以在Image中保持,并在容器中的环境变量里(死)

FROM ubuntu:21.04
ENV VERSION=2.0.1  # ARG也是这种用法
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

合理使用缓存/.dockerignore

  • 缓存

第二步如果改动了,后面都不会使用缓存,所以可以适当的换位置

把容易改变的放到后面,固定的放在前面,方便使用缓存。

  • .dockerignore 类似git中的忽视

docker image build -t demo .中的. 这个参数就是代表了build context所指向的目录

# 把不需要的直接忽略了
.vscode/
env/

多阶段构建/非Root

  • 多阶段构建
# gcc镜像非常的大1.14GB.
# 实际上当把hello.c编译完以后,并不需要这样一个大的GCC环境,一个小的alpine镜像就可以了。
# 这时候使用多阶段构建。AS和重新From 
FROM gcc:9.4 AS builder
COPY hello.c /src/hello.c
WORKDIR /src
RUN gcc --static -o hello hello.c

FROM alpine:3.13.5
COPY --from=builder /src/hello /src/hello
ENTRYPOINT [ "/src/hello" ]
CMD []
  • 非Root

用户有执行docker的权限,可以通过Docker做很多越权的事情了,

比如,我们可以把这个无法查看的/root目录映射到docker container里,你就可以自由进行查看了。

$ docker run -it -v /root/:/root/tmp busybox sh

这个更猛!安全问题:

image-20220107210725809

Docker的存储

Docker主要提供了两种方式做数据的持久化

  • Data Volume, 由Docker管理,(/var/lib/docker/volumes/ Linux), 持久化数据的最好方式
  • Bind Mount,由用户指定存储的数据具体mount在系统什么位置

容器停止的话文件还在但是删除了就没有了。

Data 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
WORKDIR /app

VOLUME ["/app"] # 新的语法不使用 -v 才生效

# RUN cron job
CMD ["/usr/local/bin/supercronic", "/app/my-cron"]

$ docker volume ls # 查看名字

$ docker volume inspect $(name) # 去看MountPoint

$ docker volume run -d -v $(volumeName):$(path) $(name) # 在创建的时候指定名称

# 如果删除了容器重新创建的时候还想数据继续
$ docker volume run -d -v $(volumeName):$(path) $(name) # 不变则会去找对应路径下的数据加载进去

Bind Mount

$ docker volume run -d -v $(localPath):$(path) $(name)

# 存在当前目录下
$ docker volume run -d -v ${pwd}:$(path) $(name) # windows PowerShell
$ docker volume run -d -v $(pwd):$(path) $(name) # Mac

多机器共享数据

参考链接:https://dockertips.readthedocs.io/en/latest/docker-volume/multi-host-share.html

Docker的网络

基础内容

curl的使用 http://www.ruanyifeng.com/blog/2019/09/curl-reference.html

  • 容器可以获得自己的IP
  • 宿主机可以ping通容器的IP
  • 该宿主机上的容器之间可以ping通(同一个Bridge上)
  • 另一个宿主机ping本宿主机上的容器不行,只能靠端口转发
  • 容器可以访问外网
$ docker network ls

$ docker network inspect $(id)

为什么该宿主机上的容器之间可以ping通? 同一个docker0

为什么容器可以访问外网? docker0连接到了eth0

image-20220109192328688

Bridge/Host/端口转发

 # 创建一个新的网络
$ docker network create -d $(driver) $(name)
$ docker network create -d bridge myBridge 

# 指定哪一个网络
$ docker container run -d --rm --name $(name) --network $(networkName) $image 
$ docker container run -d --rm --name box3 --network mybridge busybox /bin/sh -c "while ture; do sleep 3600; done"

# 一个容器链接2个网络
$ docker network connect bridge box3 # 让box3这个container连接上bridge这个网络

# 关闭链接
$docker network disconnect bridge box3

# 自定义网关/网段
docker network create -d bridge --gateway 172.200.0.1 --subnet 172.200.0.0/16 demo
  • ping对方的时候可以Ping对方的名字
  • 自己建立的网络可以提供一个DNS的服务(原生的不行)
  • Dockerfile中的Exopse更多起到的是注释的作用
$ docker container run -d --rm --name -p 8080:80 web nginx # 外部的8080映射到内部80
  • 如果使用了Host网络,container与主机共享同一个网络,相当于在本地启动了个容器。
  • 使用了Host网络,一个端口只能使用一次。

Docker compose

使用.yaml文件简化部署

docker compose文档 https://docs.docker.com/compose/compose-file/

模版/例子/使用/提前准备image

version: "3.8" # 自己定义版本

services: # 容器
  servicename: # 服务名字,这个名字也是内部 bridge网络可以使用的 DNS name
    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

以 Python Flask + Redis练习:为例子,改造成一个docker-compose文件

version: "3.8"

services:
  flask-demo:
    # 当使用不存在的image且是自己创建的image时
    # build: ./flask # 加上了image: flask-demo:latest会重新命名为指定的
    # build: # 如果需要指定名字
      # context: ./flask # 指定目录
      # dockerfile: $(name) # 指定名字
    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.yml除非用-f指定
  • 使用命令行开始
$ docker-compose up # 启动但是没法后台运行

$ docker-compose up -d # 后台运行
$ docker-compose up -d -p myProject up # 更改前缀

# 后台运行的话我们想看结果的话
$ docker-compose logs
$ docker-compose logs -f # 动态查看

$ docker-compose ps # 查看状态

$ docker-compose rm # 删除用compose创建的停止的容器

$ docker-compose restart # 重启,volume更新

# 常见运行姿势
$ docker-compose pull
$ docker-compose up -d --build

对于提前准备好image使用 docker-compose pull 就会在当前文件夹下根据yml文件开始创建

文件更新/网络

对于已经在运行的container,更改了本地的文件之后呢

还是可以继续使用docker-compose up -d --build

这个会查看镜像,重新创建修改了的,然后重新开启。

如果添加了新的imagedocker-compose up -d就会拉取新的

如果添加了之后再删除 docker-compose up -d --remove-orphans删除不需要的


如果没有指定的话会自己创建一个自己的bridge网络,自动完成一系列注册

Network可以自己配置 参考链接

水平扩展/环境变量

快速增加存在的serve的数量

$ docker-compose up -d --scale flask=3 # 将flask这个服务增加到3个

当去访问服务的时候,docker帮忙做了一个负载均衡


针对环境变量:https://docs.docker.com/compose/environment-variables/

如果直接将密码写在了yml文件中就不是很安全,则需环境变量的传递

在所在的路径里面创建.env的文件REDIS_PASSWORD=abc123

docker-compose会自己去找,使用docker-compose config可以验证

同时可以手动指定 docker-compose --env-file ./my.env congif

services:
  flask-demo:
    image: flask-demo:latest
    environment:
      - REDIS_HOST=${
    
    REDIS_PASSWORD} # 我们手动赋值

服务依赖和健康检查

服务依赖使用关键词depend_on实现启动顺序

健康检查:

docker container inspect中的health可以看见情况

  • 在dockerfile中:

    HEALTHCHECK --interval=30s --timeout=3s \
        CMD curl -f http://localhost:5000/ || exit 1
    
  • 在compose中

    healthcheck:
          test: ["CMD", "curl", "-f", "127.0.0.1:5000"]
          interval: 1s
          timeout: 3s
          retries: 30
    

根据健康检查实现服务依赖

depend_on:
  flask:
    condition: service_healthy

Docker swarm

基本架构:

image-20220112202254069

单节点与多节点

docker info 可以查看docker engine有没有激活swarm模式

激活swarm,有两个方法:

  • 初始化一个swarm集群,自己成为manager,使用docker swarm init
  • 加入一个已经存在的swarm集群

如果在container里面杀死了一个,swarm会重新再启动一个维护数量

$ docker swarm init # 初始化集群环境

$ docker node ls  # 在集群模式下

$ docker swarm leave --force # 离开集群

$ docker service create nginx:latest # 创建一个service

$ docker service ls

$ docker service ps $(id) # 具体看某个服务

$ docker service update $(id) --replicas n # 将这个id下的service里面准备到n个container

$ docker service rm $(id) # 删除服务

而针对创建3节点swarm cluster的多个节点的话:

https://labs.play-with-docker.com/ 可以有4个小时时间使用实例

$ docker swarm init --advertise-addr=192.168.200.10 # 实例一创建manager

$ docker swarm join --token xxx # 实例二与三加入作为worker有的命令只能在manager上面执行

$ docker service create --name web nginx

$ docker service ps web # 可以看到在worker与manger中随机一个创建

$ docker service logs $(name) # 查看日志

overlay /ingress 网络

创建指定网络的service中的分布到manager与worker上的container会连接在overlay与自家的bridge网络上

  • 第一是外部如何访问部署运行在swarm集群内的服务,可以称之为 入方向 流量,在swarm里我们通过 ingress 来解决
  • 第二是部署在swarm集群里的服务,如何对外进行访问,这部分又分为两块:
    • 第一,东西向流量 ,也就是不同swarm节点上的容器之间如何通信,swarm通过 overlay 网络来解决;
    • 第二,南北向流量 ,也就是swarm集群里的容器如何对外访问,比如互联网,这个是 Linux bridge + iptables NAT 来解决的
$ docker network create -d overlay mynet # 创建overlay网络

$ docker service create --network mynet --name test --replicas 2 busybox ping 8.8.8.8 # 创建指定网络的service

ingress为了实现把service的服务端口对外发布出去,让其能够被外部网络访问到。

实现的效果:在manger或者worker(即为在swarm)中都会进行负载均衡然后访问到每个container

具体的转发规则,视频里面学了下网络的原理。(简单来说有个网络空间)

image-20220113215108737

手动部署多service应用/stack集成部署

swarm不能现成构建镜像,只能直接拉取/准备好

# 第一步: 创建一个mynet的overlay网络

# 第二步: 创建一个redis的service
$ docker service create --network mynet --name redis redis:latest redis-server --requirepass ABC123

# 第三步: 创建一个flask的service
$ docker service create --network mynet --name flask --env REDIS_HOST=redis --env REDIS_PASS=ABC123 -p 8080
:5000 xiaopeng163/flask-redis:latest
# 开发环境先清理
$ docker system prune -a -f

# 通过stack启动服务
$ env REDIS_PASSWORD=ABC123 docker stack deploy --compose-file docker-compose.yml flask-demo
Ignoring unsupported options: build

# 通过stack查看service
$ docker stack ps flask-demo
$ docker stack services flask-demo

swarm中使用 secret保护敏感信息/本地volume

  • 从标准的收入读取
$ echo abc123 | docker secret create mysql_pass -

$ docker secret inspect mysql_pass # 存在RAFT数据库中了
  • 从文件读取
$ docker secret create mysql_pass mysql_pass.txt
  • 使用
$ docker service create --name mysql-demo --secret mysql_pass --env MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql_pass mysql:5.7

产生的volume只会产生在分配的那个本地,清理swarm之后,在分配的那个主机上的volume并不会清除

其他事项

多架构

可基于多种CPU架构进行编译,docker有好的优化,会自动选择。

拉取镜像的时候,docker会自己选择。

image-20220115200342582

如果在自己电脑上想构建出别的架构的image的话。可以使用亚马逊提供的虚拟机服务

image-20220115201035284
# 以前的提交方式是不行的 会进行覆盖
# 使用buildx进行构建
# 在dockerfile的文件下

# 查看bulidx的环境
$ docker buildx ls

# 创建新的buildx的环境
$ docker buildx create --name mybuilder --use

# 使用新的环境构建(会拉取对应环境的架构进行构建)
$ docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 -t xiaopeng163/flask-redis:latest .

# 构建是在拉取的一个容器里面
# 所以取消的话 先强制删除容器 再删除buldx环境
$ docker container rm -f $(id)
$ docker build rm mybuild

Git和容器(工作流)

在Gihub账号关联之后在Github上存放代码,dockerhub会监控,更新的话就会自动构建

但是这玩意要钱!!

可以使用Github Action还可以解决多架构的问题

name: Docker image buildx and push
on: [push]
jobs:
  Docker-Build-Push:
    runs-on: ubuntu-latest
    steps:
      -
        name: Checkout
        uses: actions/checkout@v2
      -
        name: Set up QEMU
        uses: docker/setup-qemu-action@v1
      -
        name: Set up Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v1
      -
        name: Login to DockerHub
        uses: docker/login-action@v1 
        with:
          username: ${
    
    {
    
     secrets.DOCKERHUB_USERNAME }}
          password: ${
    
    {
    
     secrets.DOCKERHUB_PASSWORD }}
      -
        name: Build and push
        id: docker_build
        uses: docker/build-push-action@v2
        with:
          push: true
          platforms: linux/amd64,linux/arm64
          tags: xiaopeng163/flask-redis:latest

安全

docker的安全应该算是一个很重要的问题了吧,饱受诟病

被莫名其妙拿去挖矿的数不胜数

docker-bench-security安全检查工具 https://github.com/docker/docker-bench-security

代码扫描 https://snyk.io/

镜像扫描 https://github.com/aquasecurity/trivy#os-packages

trivy官网 https://aquasecurity.github.io/trivy/

容器运行监控 https://sysdig.com/

猜你喜欢

转载自blog.csdn.net/weixin_51485807/article/details/122517156