参考链接: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
$ docker run -it $(name)
$ docker container exec -it $(id) sh
$ docker system prune -f
$ docker image prune -a
镜像的创建管理和发布
一些Image的基本使用
$ docker image pull nginx
$ docker image ls
$ docker image pull nginx:1.20.0
$ 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 image build -t hello:1.0 ./
$ docker image build -f dockerFile -t hello:1.0 ./
$ docker image rm $(name):$(tag)
$ dokcer image $(oldName) $(DockerId)/$(oldName):1.0
$ docker login
$ docker image push $(name):$(tag)
通过commit从容器创建image
$ docker container commit $(id) $(name)
$ docker container run -it ubuntu
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)
$ 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里面
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
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 可以用来设置容器启动时默认会执行的命令。
$ docker container run --rm -it $(name) $(cmd)
# 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
第二步如果改动了,后面都不会使用缓存,所以可以适当的换位置
把容易改变的放到后面,固定的放在前面,方便使用缓存。
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 []
用户有执行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)
$ 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)
$ docker volume run -d -v $(pwd):$(path) $(name)
多机器共享数据
参考链接: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"
$ docker network connect bridge box3
$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
- 如果使用了Host网络,container与主机共享同一个网络,相当于在本地启动了个容器。
- 使用了Host网络,一个端口只能使用一次。
Docker compose
使用.yaml文件 简化部署
docker compose文档 https://docs.docker.com/compose/compose-file/
模版/例子/使用/提前准备image
version: "3.8"
services:
servicename:
image:
command:
environment:
volumes:
networks:
ports:
servicename2:
volumes:
networks:
以 Python Flask + Redis练习:为例子,改造成一个docker-compose文件
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.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
$ docker-compose restart
$ 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
当去访问服务的时候,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
$ docker service ls
$ docker service ps $(id)
$ docker service update $(id) --replicas n
$ docker service rm $(id)
而针对创建3节点swarm cluster的多个节点的话:
https://labs.play-with-docker.com/ 可以有4个小时时间使用实例
$ docker swarm init --advertise-addr=192.168.200.10
$ docker swarm join --token xxx
$ docker service create --name web nginx
$ docker service ps web
$ 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
$ docker service create --network mynet --name test --replicas 2 busybox ping 8.8.8.8
ingress为了实现把service的服务端口对外发布出去,让其能够被外部网络访问到。
实现的效果:在manger或者worker(即为在swarm)中都会进行负载均衡然后访问到每个container
具体的转发规则,视频里面学了下网络的原理。(简单来说有个网络空间)
手动部署多service应用/stack集成部署
swarm不能现成构建镜像,只能直接拉取/准备好
$ docker service create --network mynet --name redis redis:latest redis-server --requirepass ABC123
$ 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
$ env REDIS_PASSWORD=ABC123 docker stack deploy --compose-file docker-compose.yml flask-demo
Ignoring unsupported options: build
$ 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
$ 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的话。可以使用亚马逊提供的虚拟机服务
$ docker buildx ls
$ docker buildx create --name mybuilder --use
$ docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 -t xiaopeng163/flask-redis:latest .
$ 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/
|