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

  1. linux 上安装 Docker 的脚本:get.docker.com

  2. 在家目录中执行:

    curl -fsSL get.docker.com -o get-docker.sh

  3. sudo sh get-docker.sh 执行安装。

  4. sudo docker version 只看到 Client,没有看到 Server。这是因为 Docker 的 Service 还是没有启动。

    1. sudo systemctl start docker 启动 Docker。

    2. 然后 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 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

 

posted @ 2024-06-23 06:24  PEAR2020  阅读(1)  评论(0编辑  收藏  举报