Develop with Docker

概要

1. Why Develop with Docker
2. Best Practice
3. Example

为什么Develop with Docker

从Docker的特性中获益
快速搭建开发环境

一般的开发过程中,我们往往需要一个SDK来build打包代码,需要一个runtime来运行build result,开发不同的工程,还可能需要安装不同版本的SDK/Runtime。

例如开发前端工程时,可能会有需要同时运行两个依赖不同node版本的工程。
或者开发前端时需要一个本地运行的后端且不想要安装整套开发的工具,快速地在本地部署一个其他工程的依赖

把复杂应用的打包,运行的依赖、步骤都保存在DOCKERFILE中,尽可能地让代码在每个开发环境中的行为都保持一致

开发体验
  1. 通过docker的layer缓存,加速开发时应用的启动时间
  2. 可以实现几乎和本地运行无差别的开发体验,hot reload, debug
  3. 开发一些需要启动多个app的工程时,使用docker compose实现一行命令启动,预配置

Best Practice

1. 选用合适的base image

使用更小更专用的的base image来build,比如镜像只是需要build工程,而不需要运行,那么我们只需要引入SDK

当多个镜像有许多一样的layer时,构建一个内部公用的base镜像,相同的layer加载一次之后就会被缓存在内存中,那么build多个不同工程时就不会去重新拉取镜像,从而加快启动速度。

2. 使用Multi-Stage

核心是在进入下一个layer的时候清除不需要的组件,理想情况下每个layer只引入上一个layer的结果和当前依赖的组件。
https://docs.docker.com/develop/develop-images/multistage-build/

多阶段构建(multi-stage builds),将构建过程分为多个阶段,每个阶段都可以指定一个基础镜像,这样在一个Dockerfile就能将多个镜像的特性同时用到,例如:先用SDK镜像构建.NET Core工程,再把构建结果和Runtime 合成,最后得到一个可以直接运行.NET Core工程镜像

(17.05前的版本可以维护多个DockerFile来实现)

FROM microsoft/dotnet:2.1.300-sdk AS builder
WORKDIR /App
COPY NuGet.config ./
COPY App.csproj ./
RUN dotnet restore
COPY . ./
RUN dotnet publish --configuration Release --output releaseOutput --no-restore

#build runtime image
FROM microsoft/dotnet:2.1.0-aspnetcore-runtime as runtime
WORKDIR /App
COPY --from=builder /App/releaseOutput ./
ENTRYPOINT ["dotnet", "App.dll"]

使用COPY指令引入builder阶段的文件

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

使用–target参数来让Docker停在某一Stage,让一份DOCKERFILE能够生成不同的镜像(比如生成debug build/release build),共用其他初始stage

docker build --target builder -t Vic/App:latest .
3. 合并多个RUN来减少Layer
RUN apt-get -y update
RUN apt-get install -y python

RUN apt-get -y update && apt-get install -y python

当某个layer被修改,docker会将所有后续的layer重新构建,而跳过之前的layer(已被缓存的)
但当多一个命令的其中运行失败时,docker就会重新执行整个run,所以需要合理设计,尽可能的将更通用的,修改可能性更小的,更长的命令放在头部
使我们修改靠后命令时可以更快build

更少的layer数量可以缩短build时间,减少镜像体积

Example

实现开发中的hot relad
  1. 首先需要给package.json加入一个新的scipts
"start:docker": "ng serve  --host 0.0.0.0 --disableHostCheck --port 9001 --poll",

由于angular dev server以容器的方式运行,我们(宿主机访问)需要通过docker compose network中转,再将请求发至angular dev server,对于angular dev server来说不是通过localhost访问的。

故需要加入参数--host 0.0.0.0 + --disableHostCheck,开放任意ip访问 + 关闭用于 DNS 重绑定的 HTTP 请求的 HOST 检查。

(DevServer 默认只接受来自本地的请求,关闭后可以接受来自任何 HOST 的请求)

由于我们使用类似于共享路径的方式(通过docker volumes挂载本地的代码目录),在容器中运行的dev server无法检测到文件变动的事件,故需要加入--poll 参数来让服务端主动轮询文件的变动信息,来实现hot reload,也就是说无法实现recompile and reload on save了。但实际即使设置较短的轮询时间,也不会对dev server的性能产生非常大的影响,所以感官上差别不大。

  1. 创建一个angular dev server专用的dockerfile
FROM node:lts AS build-angular-development
WORKDIR /app

EXPOSE 9001 49153

COPY Switches.AngularUI/package*.json ./

RUN npm install

CMD ["npm", "run", "start:docker"]

这里注意需要额外暴露49153,是 Webpack Hot Reload Module 的默认端口
当然也可以通过--live-reload-port手动设置一个新端口

这里我们不使用copy指向把代码引入到镜像中,而通过下面docker compose挂载volume来让容器获取到代码

  1. 创建docker-compose.development.yml
version: "3"

services:
  switches-angularUI:
    container_name: Angular-Dev
    image: "ymhvic/switches-development-angular"
    restart: always
    networks:
      switches_network:
        ipv4_address: 172.16.1.0
    build:
      context: .
      dockerfile: Dockerfile.Development.Angular
    ports:
      - "9001:9001"
      - "49153:49153"
    volumes:
       - ./Switches.AngularUI:/app
  switches-api:
    container_name: APIServer-Dev
    image: "ymhvic/switches-development-api"
    depends_on:
      - redis
    restart: always
    networks:
      switches_network:
        ipv4_address: 172.16.1.1
    build:
      context: .
      dockerfile: Dockerfile.Development.API
    ports:
      - "9080:80"
      - "9443:443"
    volumes:
       - ./Switches:/app/Switches
       - ./Switches.Data:/app/Switches.Data
  redis:
    container_name: redis-Dev
    image: "redis:latest"
    restart: always
    networks:
      switches_network:
        ipv4_address: 172.16.1.2
    ports:
      - "6379:6379"

networks:
  switches_network:
    ipam:
      driver: default
      config:
        - subnet: 172.16.0.0/16



- ./Switches.AngularUI:/app挂载宿主机的Switches.AngularUI目录挂载到容器的app目录下

容器间通信

在docker-compose中加配置network,每个加入同个network的容器都可以通过container_name来实现访问
比如

container_name:APIServer
ports:
-"9080:80"

那么可以其他容器可以通过http://APIServer:80 访问到

NetCore 也是同理,通过Volumn将开发目录挂载到容器中,使用dotnet watch实现hot reload

Debugging Dotnet Watch processes inside of a Docker container from csharp

在dockerfile中设置环境变量DOTNET_USE_POLLING_FILE_WATCHER

ENV DOTNET_USE_POLLING_FILE_WATCHER 1

来让dotnet使用主动的文件变动轮询

If set to “1” or “true”, dotnet watch uses a polling file watcher instead of CoreFx’s FileSystemWatcher. Used when watching files on network shares or Docker mounted volumes.

CMDs

docker run -it –rm -v $(pwd):/app -p 5001:5001 docker.io/ymhvic/switchesapihotreload
docker build . -t ymhvic/switchesapihotreload –file Dockerfile.Development.API.HotReload