Docker
背景
1. problem
1) 启动很慢 2)很耗费内存
只能local,不适合线上 => 如图所示,需要在生产服务器上install JVM + python解释器 + golang exe文件
=> bad isolation
=> 连同操作系统一起隔离
容器其实是进程processes
-
docker top c03
# 显示c03这个container 内部所有进程 - pstree -halps PID //查看宿主机上的进程依赖关系
-
容器内部的进程ID与在容器外通过docker top命令看到的进程ID不同
查看容器内部的进程ID:
docker run -it busybox sh
ps
一个image包含了一整个执行环境+执行文件, 不可更改!
repo:image仓库。
- public:docker hub; private: 公司私有搭建
spring很慢,需要加载容器,为了适应云原生-> spring native -> 可执行文件
容器: 实例,可以init 多个实例。
1. build image 2. publish to private image repo 3. pull image to server -> init an instance
=> CI/CD
容器如果被删除,会删除一切记录,所以对于有状态服务(like mysql),需要把重要数据挂载到宿主机的目录下。
即使容器被删除,也有状态记录。
1. install on linux
-
linux 上安装 Docker 的脚本:get.docker.com
-
在家目录中执行:
curl -fsSL get.docker.com -o get-docker.sh
-
sudo sh get-docker.sh 执行安装。
-
sudo docker version 只看到 Client,没有看到 Server。这是因为 Docker 的 Service 还是没有启动。
-
sudo systemctl start docker 启动 Docker。
-
然后 sudo docker version 可以看到 Client 和 Server 的输出。
-
操作命令
RUN & EXEC
docker exec -it
是在 existing 运行中的容器中执行命令,适用于调试和运行额外命令。docker run -it
是创建并启动一个新的容器实例,如果image不存在则包含 pull image,并在其中运行命令(适合临时)- docker run -it ubuntu
>>> 默认运行CMD(可改的参数),ENTRYPOINT(命令)
>>> 如果没有,默认执行shell
,等同于 docker run -it ubuntu sh
,等同于 docker run -it ubuntu /bin/sh
- docker run -it ubuntu
拉取镜像
docker pull ubuntu
docker pull ubuntu:版本
sudo docker pull ubuntu:版本 // if not root
运行容器:create & start
docker run -it --rm ubuntu bash // 运行容器进入bash界面
-it
两个参数,-i进入交互操作,-t终端
-rm
容器退出后,直接删除,说明是一次性容器
退出容器
exit
查看images
docker images | grep ubuntu
删除images
docker image rm id
docker image rm $(docker images -q ubuntu)
$()表示执行子命令
-q:quiet mode,只列出IDs
并且过滤出ubuntu
看容器的状态
sudo docker ps -a
-a:列出all including已经关闭的容器
容器里运行命令行 echo "123"; run的命令:local没有的直接pull
docker run ubuntu:18.04 echo "123"
运行容器里的bash,-it进入交互模式
docker run -it ubuntu:18.04 /bin/bash
终止容器
docker stop [containerID]
docker start [containerID]
docker restart [containerID]
后台运行容器
docker run ubuntu:18.04 /bin/sh -c "while true; do echo hello; sleep 1; done"
-c:-command
ctrl+c 退出容器
docker run -d ubuntu:18.04 /bin/sh -c "while true; do echo hello; sleep 1; done"
-d:detached mode (对应前台是attached mode)
查看日志
docker logs [containerID开头三位]
-f: 动态logging
删除容器
docker rm [containerID] // 如果正在运行,不可以remove
docker rm -f [containerID] [containerID] // -force
docker rmi [containerID/container_name] [containerID/container_name] [containerID/container_name]
进入容器
docker exec -it [containerID] /bin/bash or
docker exec -it [containerID] /bin/sh
alias
docker image tag A B
# 把A tag 成B,类似于创建alias;不创建新的container,但多一个名字
layer
docker image history ginserver:latest
# 看分层,每层多少size
镜像的获取
1. docker pull xxx
-
push image to docker_hub:
-
docker login
docker image push [name]:[tag]
-
2. build from dockerfile
3. load from file
4. container commit to image
---
3. load from file
-- save image
docker save nginx:1.2 -o nginx.image
-o: output
--- load image
docker load -i nginx.image
-i: input
4. container里有修改,希望能保存进new image:
docker container commit [ID] [newImage_Name]:[TAG]
Docker网络
服务器上pull的image是对外隔离的,无法使用8081 访问
应该image映射到宿主机的8082上才能访问
端口映射
-p hostport:containerport // 指定端口mapping
-P // available port mapping, 无需指定
-
-p:等同于 --publish
-P: 等同于 --publish-all
docker run -p 81:80 nginx:alpine
- Host Port (81): This is the port on your local machine (the host) that you want to use to access the service running inside the container.
- Container Port (80): This is the port inside the Docker container where the Nginx web server is listening for incoming requests.
docker run -d -P nginx:alpine
docker ps -a => 看到34545端口 -> 80/TCP => curl http://[IP]:34545 => success!
查看内部端口80映射到了哪里
docker port [containerID] 80
多端口映射
docker run -d -p 80:80 -p 8082:8082 nginx:alpine
如果不-p 80:80, docker不提供对外暴露的端口!
外部文件挂载
e.g. nginx里的index.html替换成宿主机的hello.html替换成宿主机的hello
docker run -it [containerID] /bin/sh
vi /etc/nginx/nginx/conf.d/default.conf //location有index.html的位置
docker rm -f 原来的containerid
docker run -d -P -v /root/index/hello.html:/usr/share/nginx/html/index.html nginx:alpine
-v [local absolute path]:[nginx path] [imageName]
curl http://xxx
确认是否挂载成功
docker exec -it [containerID] /bin/sh
ls /usr/share/nginx/html // 只有hello.html,index.html已经被覆盖
-v 本地目录覆盖
目录覆盖一定要慎重!
detailed info
docker inspect [id]
设置一个环境变量 -e [name]="[env_name]"
docker run -d -P -e mypath="bobby" -v [localpath]:[containerpath] [imageName]
docker exet -it [containerID] /bin/sh
echo $mypath
设置container_name --name [name]
DockerFile
构建镜像
touch dockerfile
vim dockerfile
-----
FROM & RUN
FROM nginx
# pull image from nginx
-
FROM scratch # 从空的构建,只能执行static binaries; mininal size
RUN echo '这是一个本地构建的nginx镜像' > /usr/share/nginx/html/index.html
# 替换index.html为一句话
RUN ["executable file","-c","config/user/src.yaml"]
# Run the user-server with the specified configuration file during the build process
# 在运行的过程中装一个python or go
-------
mv dockerfile build/
cd build/
docker build -t mynginx . #从当前目录build/来构建image
-t: tag/name
docker image ls # 看到自己构建的mynginx
docker run -d --name nginx-test -P mynginx # --name 要写在image name前面
docker ps -a
curl http://xxxx:xxx # '这是一个本地构建的nginx镜像'
COPY : 本地文件拷贝入镜像
copy sourcePath targetPath
copy hom* /src/
copy file?.txt /src/
e.g.
build目录下有dockerfile,500.html
vim dockerfile
FROM ngnix
COPY 500.html /usr/share/nginx/html/500.html
COPY & ADD
both:
- if not exist dir, create;
- 权限复制
ADD: 自动解压
e.g. ADD hello.tar.gz /app/
Build
docker build -t mynginx2 . # build image2
-
docker image build -f A -t B .
>>> 在当前目录,from A to B
docker run -d --name myngnix2_test -P mynginx2 # run container myngnix2_test
docker exec -it [containerID] /bin/sh
cat /usr/share/nginx/html/500.html
exit
e.g 构建gin的镜像
1. 构建本地的gin
vim go.mod
----
module myserver
go 1.19
---
vim main.go
# gin的服务
go run main.go
>> no required module
go mod tidy
# 没有的module就补上
go run main.go
curl http://120.0.0.1:8080/ping
>> 本地运行gin成功
go build main.go
2. vim dockerfile
RUN & CMD
CMD: docker run的过程中;写参数可覆盖
-
CMD不直接支持shell风格的变量替换 =>
CMD ["sh","-c","echo ${NAME}"]
-c: 接下来的字符当作命令执行 - ENTRYPOINT: 写命令不可覆盖,只能追加
RUN: docker build的过程中
RUN
尽量使用一个命令,不要分层!
e.g
RUN apt-get update && \
apt-get install -y wget && \
wget xxx && \
...
---
FROM alpine
COPY main /usr/
RUN ["","",""] => 并非image的配置构建
CMD ["/usr/main"] => container运行的时候才执行的executable file
---
3. build image
docker build -t MyGinServer
docker run -d --name ginContainer -P MyGinServer
4. 问题:外部端口 -> 80/TCP,内部是8080,要改变内部的端口
1. 重新go build
1) 禁用CGO: 保证纯静态二进制文件
2) GOOS: 目标操作系统
CGO_ENABLED=0 GOOS=linux go build .
./main # 试验一下是否成功
2. dockerfile里增加 EXPOSE 8080
FROM alpine COPY main /usr/ EXPOSE 8080 CMD ["/usr/main"]
3. docker build -t ginServer .
4. docker run -d -P --name ginTest ginServer
ENTRYPOINT & CMD
参数写到CMD【可以被覆盖!】, 命令写到ENTRYPOINT【不被覆盖!】
CMD ["[command]","[param1]", "[param2]"]
CMD ["[executable file]"]
会被docker run时 的执行命令覆盖!
ENTRYPOINT ["cat","/etc/passwd"]
不会被覆盖!执行命令直接append到后面!
e.g
FROM alpine
CMD ["/etc/passwd"]
ENTRYPOINT ["cat"]
---
docker build -t test1 .
docker run -it test1 # cat /etc/passwd
docker run -it test1 /etc/shadow # cat /etc/shadow
ENV & ARG
- ARG: 定义构建时的参数,这些参数在构建阶段可用,但在运行时不可用。
- ENV: 定义环境变量,这些变量在构建阶段和运行时都可用
ENV写在dockerfile里: ENV MODE=production
docker run --rm -e MODE=dev image_name
# 运行时修改
-----
ARG在dockerfile里:
ARG VERSION=7.2
RUN curl XXX${VERSION} & curl XXX
# 是一个默认值
docker build -t image_name --build-args VERSION=7.3 .
# build image阶段可改
WORKDIR
WORKDIR "/usr/data"
---
docker run -it test_workdir /bin/sh
>>> 默认目录在/usr/data 下
dockerfile best practices
0. 选择基础镜像
1. official
2. 固定版本tag,而不是latest
3. low size
1.合理利用缓存
dockerfile中,把容易发生变化的COPY 文件A 目录B
放到后面,前面放可以缓存的e.g. RUN pip install flask
# 因为一旦发生修改,后面全部不用cache,重新下
2. build .
. -> 为了减小build context
增加.dockerignore: 忽略不重要的文件夹
.vscode/
env/
2. multi-stage build: 适合从编译到运行阶段
# 第一阶段:编译阶段 FROM golang:1.18 AS builder # 设置工作目录 WORKDIR /app # 复制所有文件到工作目录 COPY . . # 编译 Go 应用,生成静态链接的二进制文件 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o myapp . # 第二阶段:运行阶段 FROM alpine:latest # 复制编译后的二进制文件到最终镜像中 COPY --from=builder /app/myapp /myapp # 设置启动命令 CMD ["/myapp"]
3. 使用非root用户运行容器
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run()
app.py
FROM python:3.9.5-slim RUN pip install flask && \ groupadd -r flask && useradd -r -g flask flask && \ mkdir /src && \ chown -R flask:flask /src USER flask WORKDIR /src COPY app.py . EXPOSE 5000 CMD ["flask", "run", "-h", "0.0.0.0"]
.dockerignore
docker build -t testimage .
docker run -it --name testserver testimage
$ // 代表非root