Getting Started

推送自己的image

和github类似,首先有一个hub来作为仓库,存储我们的image,以docker官方的hub为例,在https://hub.docker.com/上注册账号,并创建好repo。

登录账号

docker login -u YOUR_USER_NAME

在本地构建好镜像后,执行:

docker push YOUR_USER_NAME/repo_name

初次执行时,会执行失败,这是因为当前没有叫做YOUR_USER_NAME/repo_name的镜像(就可以docker image ls查看所有镜像),需要执行

docker tag getting-started YOUR_USER_NAME/repo_name

docker tag 用于给镜像打标签,语法如下:

https://www.kancloud.cn/noahs/linux/1557706

docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]

类似于本地版本控制,可以为镜像打不同的tag,这样可以执行不同版本的镜像。同时可以关联到不同的仓库下,打完tag后,可以直接使用这个新tag来执行push和pull。

如果执行tag的时候没有给出tagname(冒号后的tag),那么docker会默认指定为latest

容器volume

volume是指容器用来挂载的空间,如果不挂载到宿主机的某个目录下,容器挂掉后,数据是没有持久化存储的,而且容器之间数据是不共享的,如果两个容器挂载了同一个目录,那么容器间就可以共享文件,和执行共同的文件操作。

命名volume

命名volume是指用名字指代的volume,类似于命名管道一样,不需要知道他具体挂载到了哪里,只要使用同一个名字的多个容器,都可以进行共享。

首先创建命名volume:

docker volume create todo-db

在启动容器时,需要指定volume挂载到宿主机哪个目录

docker run -dp 2000:3000 -v todo-db:/etc/todos getting-started

-v参数后跟的是 本地挂载路径:容器中的路径

由于是命名volume所以不知道确切的本地路径,而在容器中的/etc/todos进行文件操作,能够在宿主机和挂载了todo-db的其他容器中可见

查看命名volume的信息

docker volume inspect todo-db
# output
[
    {
        "CreatedAt": "2021-11-29T12:55:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

因为是命名volume,我们没有显示的指定挂载的路径,所以docker会帮我们决定一个路径,这个路径通常只能root用户访问。

我们可以不用docker volume create,直接在docker run的时候使用一个新的命名volume名字,docker会自动识别,会自动帮我们创建一个新的命名volume。

绑定挂载

这个其实就是我们指定本机的一个路径,然后和容器中的一个路径映射,也就是上面的命名volume改成一个本机路径而已

docker run -d -v "$(pwd)/helloworld:/container/dir" image:tag

命名volume和绑定挂载的区别:

需要注意的是,如果挂载的本机路径没有容器需要的东西,例如getting_started这个镜像需要/app/package.json才能正常启动,如果此时-v /my_dir:/app,如果my_dir下没有package.json,那么会启动失败,因为容器找不到该文件:

docker run命令行参数

-it:表示interactive 以交互的方式运行容器

-d:detach,表示后台运行容器

-p:端口映射,宿主机端口:容器端口,例如2000:3000,访问宿主机的2000端口,就会访问到容器,容器里的服务绑定的端口为3000

-w:指定容器的工作路径,-w /app,同时也是命令(通常是sh)的工作路径

-e:指定容器执行所需的环境变量,-e MYSQL_ROOT_PASSWORD=secret

查看容器日志

docker logs container_id

docker logs -f container_id 能够实时查看日志,类似tail -f

或者

1、获取日志文件路径

docker inspect --format '{{.LogPath}}' container_id

2、打开日志文件

进入容器内部

首先要容器已经启动了

然后

docker exec -it container_id /bin/bash

不一定所有的容器都可以执行/bin/bash这,要和具体的镜像来看。

例如,mysql的镜像不包含这个sh可执行程序,不能这么交互,在mysql镜像中/bin/bash这个命令要相应替换成mysql -hxx -uxxx- pxxx

获取镜像

docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

Docker Registry 地址是指docker镜像所存放的仓库地址,默认是docker.io

不给出则使用默认值,也可以自己指定要拉取的地址

仓库名一般对应就是你的项目(也就是你的镜像名),然后标签表示镜像的版本。

查看镜像信息

docker image ls -a
docker inspect image_name:tag

返回的是一大串json信息,描述了这个镜像

容器通信

每个容器都是独立而且隔离的,是不知道别的容器的,想要容器之间通信,那么就需要使用network,只有在同一个网络的容器才能通信。

容器网络有三种类型,bridge、host、null,默认创建的都是bridege类型

docker network create todo-app

上面的代码指定了一个网络,名为todo-app,后续启动容器时可以使用--network指定通信的网络:

docker run -d \
     --network todo-app --network-alias mysql \
     -v todo-mysql-data:/var/lib/mysql \
     -e MYSQL_ROOT_PASSWORD=secret \
     -e MYSQL_DATABASE=todos \
     mysql:5.7

--network-alias指定了这个容器在网络中的别名,类似于hostname,docker可以自动解析这个别名得到容器的地址和端口(相当于docker做了一个dns解析)。

在上面的启动代码中可以看到,容器不一定要向外暴露端口,也即主机不一定要访问通过ip+端口来访问容器的服务,可以容器在自己内部玩,而内部的端口也要注意不能冲突,具体内部使用的是什么端口,这是由镜像决定的,例如mysql内部用的是3306。

docker-compose

docker-compose是一个容器编排的工具,通过编写docker-compose.yml文件,按照语法和关键词设置好每一个镜像的启动方式,其实每一个容器的配置对应的都是使用docker run的命令参数。

这里给出一个简单的例子:

version: "3.7"
services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 2000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos
  
  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos
volumes:
  todo-mysql-data:

其中,version指的是当前使用的docker-compose的版本,和cmake如出一辙,起始先写好版本。services中的每一项(第二级)对应的是每一个容器的名字,这个名字就是network-alias,docker会自动为services中每一个容器创建一个network,无需我们操作指定。

在使用命名volume时,docker-compose不像docker run那样会自动帮我们创建volume,我们需要指定,所以要在第一级volumes对象中,指定volume的名字,如果没有特殊的配置,可以在冒号后省略,可配置的参数可参考:https://docs.docker.com/compose/compose-file/compose-file-v3/#volume-configuration-reference

环境变量在environment项中给出。

在使用docker-compose.yml文件启动编排的服务时,需要确保相关的相似配置的容器没有启动,否则会冲突,例如端口重复绑定。

在docker-compose.yml文件所有的目录下,执行docker-compose up -d,以运行容器,其中-d也是detach后台启动的意思。

如果想要查看容器的日志,可以使用:

docker-compose logs -f

docker-compose logs -f service_name

如果服务之间有启动的先后关系,例如先要启动了mysql,然后再启动具体服务,那么需要自己来保证,因为docker没有内置的方法来确定哪个容器已经完全起好了。

启动之后,docker-compose会默认以yml文件所在的目录名作为项目名,所以容器实例的名字格式为:

projectname_containername_replica

项目名_容器名_实例号

想要全部一次性下线服务的话,使用docker-compose down

要注意,这个命令不会去删除volume,如果要删除的话,我们需要使用标志位:

docker-compose down --volumes

一些docker实践的方法

安全性检查

使用docker scan image_name,可以检查镜像的安全性,不过检查前要登陆docker hub,使用Snyk来检查,需要连接。

查看镜像的层级

docker image history image_name

可以查看镜像每一层的大小:

输出结果中,最底层的在最下面,最顶层的在在上面。

镜像层缓存

docker镜像是一层层的结构,如果底层的已经有了,就会缓存起来,不需要再次拉取。

假设层级为A(最高)、B、C、D(最底)。

某一层级发生变化后,其上的所有层都需要重新构建,例如B变更,则A也要重新构建。可以理解成下面的是上面层的地基,地基变了,上层自然跟着变。

我们写的Dockerfile,其中每一行命令都对应了一层layer,所以我们编写的时候就需要考虑顺序问题;在Dockerfile中,从上往下阅读,越先执行的就是越底层的layer。

# 1
FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

# 2
 FROM node:12-alpine
 WORKDIR /app
 COPY package.json yarn.lock ./
 RUN yarn install --production
 COPY . .
 CMD ["node", "src/index.js"]

上面第二个的写法会比第一个好,如果我们修改了任何一个文件,此时copy . .对应的layer变更了,其上的所有layer都要变更,所以yarn要重新执行安装。

而第二种写法中,只有修改了package.json、yarn.lock文件才会执行yarn的reinstall。

使用.dockerignore文件,在文件中指定要忽略的目录或者文件名,例如:

# .dockerignore
node_modules

要保证文件名/目录要和.dockerignore文件在同一级下。

多阶段构建

https://docs.docker.com/develop/develop-images/multistage-build/

我们可能会存在使用某个镜像,来编译依赖项和最终的可执行文件,如果编译和最后的服务启动相关的环境都在同一个镜像中,就会很大,因为我们需要提供编译能力的镜像层;要么就是我们手动先用镜像A编译了代码,然后将可执行文件复制到另外一镜像,再进行启动,中间还需要写一个脚本:

https://yeasy.gitbook.io/docker_practice/image/multistage-builds

使用多阶段构建可以在一个Dockerfile中实现这个能力:

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

每个From可以指定使用不同的基础镜像,可以看到第二个Copy --from=build,表示从前面构建好的结果,复制到另外一个镜像,其中build就是第一个镜像的别名,如果不使用别名as,那么可以用索引,从0开始--from=0。

每一个from对应的是一个stage,最终镜像是最后的stage指定的内容,例如上面的在build构建后,镜像是tomcat。我们也可以在构建时,使用--target来指定。

docker build --target builder -t alexellis2/href-counter:latest .

builder是From xxx as YYY中的YYY,我们可以指定构建到某个stage。

查看容器启动命令

docker inspect container-id

docker run -itd --name test1 --network bridge --ip 172.17.0.10 centos:latest

/bin/bash