Docker自学笔记
Posted 望向天空的恒毅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Docker自学笔记相关的知识,希望对你有一定的参考价值。
参考链接:https://dockertips.readthedocs.io/en/latest/
学习链接:https://coding.imooc.com/class/chapter/511.html
既然人家写了笔记为什么还要自己写一遍?
- 为了自己写一遍熟悉下
- 自己查询起来快一点,他的笔记是挂在github上的,点一下加载下难受
- 好多注释不全,久了没用到会忘记~
首发于 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
COPY
和 ADD
都可以把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作为参数传入输出出来
构建参数和环境变量
ARG
和 ENV
几乎相同但是
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
这个更猛!安全问题:
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
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
基本架构:
单节点与多节点
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
具体的转发规则,视频里面学了下网络的原理。(简单来说有个网络空间)
手动部署多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的话。可以使用亚马逊提供的虚拟机服务
# 以前的提交方式是不行的 会进行覆盖
# 使用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/
以上是关于Docker自学笔记的主要内容,如果未能解决你的问题,请参考以下文章