From b802889ef0837ca44d9306a7574f0464e4ae2da4 Mon Sep 17 00:00:00 2001 From: Mike McDermott Date: Thu, 4 Jul 2019 03:13:48 -0400 Subject: [PATCH] - Use staged container builds to improve caching and reduce size (#727) * - Use staged container builds to improve caching and reduce size - Image size reduced from 8 GB to 1.6ish - Switched from Make to Ninja for faster builds that do not hog processor - Removed unneded dependencies - Added to .dockerignore - Readme for docker stuff - Staged Builds - Docker's overlay FS means that `rm`ing files does not reduce size - Once build artifacts are build, the build dependencies are no longer needed - Both of these can be solved by building in a temporary image and copying only the needed libraries in - Leverages DESTDIR to generate a directory structure that can be just copied onto the `/` of the filesystem - Similarly, the data files (like models) can be downloaded ahead of time into their own image and copied in. This saves on network IO. - Anything in a RUN directive that is non-deterministic (e.g. downloading a file from a link, the content of that link changes) does not cause a cache miss, so if you need to update something RUN uses, either modify the dockerfile or build with `--no-cache` to force a rebuild - Switch to Ninja - cmake can generate many types of build systems - Ninja builds faster than GNU Make - `make -j` has a tendency to lock up my system when building locally - Do not need to tell ninja how many jobs to run - .dockerignore - Paths in .dockerignore are basically invisible to dockerd, so when dockerd zips up the build context, all of the cruft can be ignored - it is beneficial to docker build speed to add any large, unnecssary files and directories to .dockerignore. - Just remember they cannot be seen by dockerd * removing cruft and some format fixes * updated dockerfile to opencv 4.1.0 --- .dockerignore | 4 +- .env | 6 +++ .gitignore | 5 +++ CMakeLists.txt | 2 +- Dockerfile | 76 ------------------------------- docker-compose.yml | 24 ++++++++++ docker/Dockerfile | 110 +++++++++++++++++++++++++++++++++++++++++++++ docker/README.md | 57 +++++++++++++++++++++++ 8 files changed, 206 insertions(+), 78 deletions(-) create mode 100644 .env delete mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile create mode 100644 docker/README.md diff --git a/.dockerignore b/.dockerignore index f6823b742..4dd75f38d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ +.git +docker-compose.yml build matlab_runners matlab_version @@ -5,4 +7,4 @@ python_scripts test-dump samples model_training -gui \ No newline at end of file +gui diff --git a/.env b/.env new file mode 100644 index 000000000..64ffed514 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +## This is read by docker-compose. You can overwrite these at runtime, e.g.: +## DOCKERUSER=bobfoo docker-compose build + +DOCKERUSER=openface +DOCKERTAG=latest +DATA_MOUNT=/tmp/openface diff --git a/.gitignore b/.gitignore index bdef7f667..14d939274 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +lib/local/LandmarkDetector/model/patch_experts/cen_patches_*.dat + matlab_version/experiments_menpo/out_semifrontal/ /x64/Release/ /x64/Debug/ @@ -100,3 +102,6 @@ lib/local/Utilities/Debug/ lib/3rdParty/CameraEnumerator/Debug/ lib/local/CppInerop/Debug/ *.user + +# IDE-generated folders +.idea diff --git a/CMakeLists.txt b/CMakeLists.txt index 26416f455..8d5e96952 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,7 +138,7 @@ endforeach() if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) if (GCC_VERSION VERSION_LESS 8.0) - MESSAGE(FATAL_ERROR "Need a 8.0 or newer GCC compiler ${GCC_VERSION}") + MESSAGE(FATAL_ERROR "Need a 8.0 or newer GCC compiler. Current GCC: ${GCC_VERSION}") else () set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse -msse2 -msse3") endif () diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4dc2c3b0f..000000000 --- a/Dockerfile +++ /dev/null @@ -1,76 +0,0 @@ -FROM ubuntu:16.04 as build - -LABEL maintainer="Edgar Aroutiounian " - -ARG DEBIAN_FRONTEND=noninteractive - -ARG BUILD_DIR=/home/build-dep - -ARG OPENFACE_DIR=/home/openface-build - -RUN mkdir ${OPENFACE_DIR} -WORKDIR ${OPENFACE_DIR} - -COPY ./CMakeLists.txt ${OPENFACE_DIR} - -COPY ./cmake ${OPENFACE_DIR}/cmake - -COPY ./exe ${OPENFACE_DIR}/exe - -COPY ./lib ${OPENFACE_DIR}/lib - -ADD https://www.dropbox.com/s/7na5qsjzz8yfoer/cen_patches_0.25_of.dat?dl=1 \ - ${OPENFACE_DIR}/lib/local/LandmarkDetector/model/patch_experts/cen_patches_0.25_of.dat - -ADD https://www.dropbox.com/s/k7bj804cyiu474t/cen_patches_0.35_of.dat?dl=1 \ - ${OPENFACE_DIR}/lib/local/LandmarkDetector/model/patch_experts/cen_patches_0.35_of.dat - -ADD https://www.dropbox.com/s/ixt4vkbmxgab1iu/cen_patches_0.50_of.dat?dl=1 \ - ${OPENFACE_DIR}/lib/local/LandmarkDetector/model/patch_experts/cen_patches_0.50_of.dat - -ADD https://www.dropbox.com/s/2t5t1sdpshzfhpj/cen_patches_1.00_of.dat?dl=1 \ - ${OPENFACE_DIR}/lib/local/LandmarkDetector/model/patch_experts/cen_patches_1.00_of.dat - -RUN mkdir ${BUILD_DIR} - -ADD https://github.com/opencv/opencv/archive/3.4.0.zip ${BUILD_DIR} - -RUN apt-get update && apt-get install -qq -y \ - curl build-essential llvm clang-3.7 libc++-dev python3 python3-pip\ - libc++abi-dev cmake libopenblas-dev liblapack-dev git libgtk2.0-dev \ - pkg-config libavcodec-dev libavformat-dev libswscale-dev \ - python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev \ - libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev checkinstall \ - libboost-all-dev wget unzip && \ - rm -rf /var/lib/apt/lists/* - -RUN pip3 install setuptools - -RUN cd ${BUILD_DIR} && unzip 3.4.0.zip && \ - cd opencv-3.4.0 && \ - mkdir -p build && \ - cd build && \ - cmake -D CMAKE_BUILD_TYPE=RELEASE \ - -D CMAKE_INSTALL_PREFIX=/usr/local \ - -D WITH_TBB=ON -D WITH_CUDA=OFF \ - -D BUILD_SHARED_LIBS=OFF .. && \ - make -j4 && \ - make install - -RUN wget http://dlib.net/files/dlib-19.13.tar.bz2 && \ - tar xvf dlib-19.13.tar.bz2 && \ - cd dlib-19.13/ && \ - mkdir build && \ - cd build && \ - cmake .. && \ - cmake --build . --config Release && \ - make install && \ - ldconfig && \ - cd .. - -RUN cd ${OPENFACE_DIR} && mkdir -p build && cd build && \ - cmake -D CMAKE_BUILD_TYPE=RELEASE .. && \ - make -j4 - -RUN ln /dev/null /dev/raw1394 -ENTRYPOINT ["/bin/bash"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..f4888b46c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +--- +# Variables can be set from the shell: `DOCKERTAG=foo docker-compose build` +# or from a .env file. See docker-compose documentation for details +# Variable DOCKERUSER should be set to your dockerhub user +# Alternatively, use a docker registry url as the image name +version: '3.7' +services: + ## Image runtime service + ## This can be used to add volume mounts or pass environment variables + ## Todo: make a service which can use the container interactively + openface: + container_name: openface + build: + context: . + dockerfile: docker/Dockerfile + image: "${DOCKERUSER}/openface:${DOCKERTAG}" + tty: true + volumes: + - "${DATA_MOUNT}:${DATA_MOUNT}" + environment: + DATA_MOUNT: "${DATA_MOUNT}" + command: ["bash"] + +... diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..cef445baf --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,110 @@ +# ==================== Building Model Layer =========================== +# This is a little trick to improve caching and minimize rebuild time +# and bandwidth. Note that RUN commands only cache-miss if the prior layers +# miss, or the dockerfile changes prior to this step. +# To update these patch files, be sure to run build with --no-cache +FROM alpine as model_data +RUN apk --no-cache --update-cache add wget +WORKDIR /data/patch_experts + +RUN wget -q https://www.dropbox.com/s/7na5qsjzz8yfoer/cen_patches_0.25_of.dat &&\ + wget -q https://www.dropbox.com/s/k7bj804cyiu474t/cen_patches_0.35_of.dat &&\ + wget -q https://www.dropbox.com/s/ixt4vkbmxgab1iu/cen_patches_0.50_of.dat &&\ + wget -q https://www.dropbox.com/s/2t5t1sdpshzfhpj/cen_patches_1.00_of.dat + +## ==================== Install Ubuntu Base libs =========================== +## This will be our base image for OpenFace, and also the base for the compiler +## image. We only need packages which are linked + +FROM ubuntu:18.04 as ubuntu_base + +LABEL maintainer="Michael McDermott " + +ARG DEBIAN_FRONTEND=noninteractive + +# todo: minimize this even more +RUN apt-get update -qq &&\ + apt-get install -qq curl &&\ + apt-get install -qq --no-install-recommends \ + libopenblas-dev liblapack-dev \ + libavcodec-dev libavformat-dev libswscale-dev \ + libtbb2 libtbb-dev libjpeg-dev \ + libpng-dev libtiff-dev &&\ + rm -rf /var/lib/apt/lists/* + +## ==================== Build-time dependency libs ====================== +## This will build and install opencv and dlib into an additional dummy +## directory, /root/diff, so we can later copy in these artifacts, +## minimizing docker layer size +## Protip: ninja is faster than `make -j` and less likely to lock up system +FROM ubuntu_base as cv_deps + +WORKDIR /root/build-dep +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update -qq && apt-get install -qq -y \ + cmake ninja-build pkg-config build-essential checkinstall\ + g++-8 &&\ + rm -rf /var/lib/apt/lists/* &&\ + update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8 + +## llvm clang-3.7 libc++-dev libc++abi-dev \ +## ==================== Building dlib =========================== + +RUN curl http://dlib.net/files/dlib-19.13.tar.bz2 -LO &&\ + tar xf dlib-19.13.tar.bz2 && \ + rm dlib-19.13.tar.bz2 &&\ + mv dlib-19.13 dlib &&\ + mkdir -p dlib/build &&\ + cd dlib/build &&\ + cmake -DCMAKE_BUILD_TYPE=Release -G Ninja .. &&\ + ninja && \ + ninja install && \ + DESTDIR=/root/diff ninja install &&\ + ldconfig + +## ==================== Building OpenCV ====================== +ENV OPENCV_VERSION=4.1.0 + +RUN curl https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.tar.gz -LO &&\ + tar xf ${OPENCV_VERSION}.tar.gz && \ + rm ${OPENCV_VERSION}.tar.gz &&\ + mv opencv-${OPENCV_VERSION} opencv && \ + mkdir -p opencv/build && \ + cd opencv/build && \ + cmake -D CMAKE_BUILD_TYPE=RELEASE \ + -D CMAKE_INSTALL_PREFIX=/usr/local \ + -D WITH_TBB=ON -D WITH_CUDA=OFF \ + -DWITH_QT=OFF -DWITH_GTK=OFF\ + -G Ninja .. && \ + ninja && \ + ninja install &&\ + DESTDIR=/root/diff ninja install + +## ==================== Building OpenFace =========================== +FROM cv_deps as openface +WORKDIR /root/openface + +COPY ./ ./ + +COPY --from=model_data /data/patch_experts/* \ + /root/openface/lib/local/LandmarkDetector/model/patch_experts/ + +RUN mkdir -p build && cd build && \ + cmake -D CMAKE_BUILD_TYPE=RELEASE -G Ninja .. && \ + ninja &&\ + DESTDIR=/root/diff ninja install + + +## ==================== Streamline container =========================== +## Clean up - start fresh and only copy in necessary stuff +## This shrinks the image from ~8 GB to ~1.6 GB +FROM ubuntu_base as final + +WORKDIR /root + +# Copy in only necessary libraries +COPY --from=openface /root/diff / + +# Since we "imported" the build artifacts, we need to reconfigure ld +RUN ldconfig diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..5ac5e4c9c --- /dev/null +++ b/docker/README.md @@ -0,0 +1,57 @@ +# Docker building instructions + +This image can be build with just `docker`, but it is highly recommend to use +`docker-compose` as this greatly simplifies and improves the process. + +## Quick start + +To start with the container hosted by the repo maintainer, run + +`docker run -it --rm --name openface algebr/openface:latest` + +This will drop you into a shell with binaries such as FaceLandmarkImg. For example, +try this code (in the container): + +```bash +curl -o tesla.jpg https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Nicola_Tesla_LCCN2014684845.jpg/559px-Nicola_Tesla_LCCN2014684845.jpg +FaceLandmarkImg -f tesla.jpg +``` + +Then, copy the output to the host system (from host terminal): + +```bash +docker cp openface:/root/processed /tmp/ +cd /tmp/processed +``` + +Tip: On Ubuntu and other *nixes with X running, you can open a file directly +like this: +```bash +xdg-open /tmp/processed/tesla.jpg +``` + +## Building + +In repo root, run `docker-compose build` to automatically build and tag. +There are two variables which can be used to modify the tag, `$DOCKERUSER` and +`$DOCKERTAG`. DC will automatically tag image as +`${DOCKERUSER}/openface:${DOCKERTAG}` + +## OpenFace service (in progress) + +To run OpenFace like a service, you can start the container with bind mounts +in order to pass data into and out of the container easily. +`$DATA_MOUNT` by default is set to `/tmp/openface`. This can be overridden by +modifying `.env` file, setting it in your shell environment, or passing in +before `docker-compose` at runtime. Note: output will be `root` owner. + +```bash +export DATA_MOUNT=/tmp/openface +mkdir -p $DATA_MOUNT/tesla # this is just to ensure this is writable by user +docker-compose up -d openface && sync # sync is to wait till service starts +curl -o $DATA_MOUNT/tesla.jpg \ + https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Nicola_Tesla_LCCN2014684845.jpg/559px-Nicola_Tesla_LCCN2014684845.jpg +docker exec -it openface FaceLandmarkImg -f $DATA_MOUNT/tesla.jpg -out_dir $DATA_MOUNT/tesla +docker exec -it openface chown -R $UID:$UID $DATA_MOUNT # chown to current user +docker-compose down # stop service if you wish +```