Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Optimizing Docker for IoT with Multi-Stage Builds

Tim Perry
December 14, 2017

Optimizing Docker for IoT with Multi-Stage Builds

Tim Perry

December 14, 2017
Tweet

More Decks by Tim Perry

Other Decks in Technology

Transcript

  1. Optimizing Docker
    for IoT with
    Multi-Stage
    Builds
    @pimterry

    View Slide

  2. @pimterry

    View Slide

  3. @pimterry

    View Slide

  4. IoT is still new,
    and still comes with
    hard problems
    @pimterry

    View Slide

  5. How do you deploy,
    monitor and manage
    IoT applications?
    @pimterry

    View Slide

  6. @pimterry

    View Slide

  7. Isolation
    Reproducibility
    Application Delivery
    @pimterry
    Why Docker + IoT?

    View Slide

  8. @pimterry

    View Slide

  9. IoT is still new,
    and still comes with
    lots of hard problems
    @pimterry

    View Slide

  10. How do you secure code that’s
    literally in your users’ hands?
    @pimterry

    View Slide

  11. How do you quickly deliver
    application changes in an
    inherently unreliable
    environment?
    @pimterry

    View Slide

  12. @pimterry

    View Slide

  13. Lightweight OS for IoT with Docker
    Optimised support for 25+ boards
    Preconfigured flexible networking support
    Secure by default throughout
    Read-only root partition + AUFS data partition
    @pimterry

    View Slide

  14. @pimterry

    View Slide

  15. @pimterry
    Moby-based container engine for IoT
    Fully Docker-compatible
    3.5x smaller than Docker
    10-70x more bandwidth efficient ‘docker pull’
    Built for low-memory environments
    Atomic & durable pulls

    View Slide

  16. Resin.io
    device
    stack
    @pimterry

    View Slide

  17. There’s only so
    much you can do
    at the device level
    @pimterry

    View Slide

  18. How can I
    build smaller images?
    @pimterry

    View Slide

  19. How can I
    hide build secrets?
    @pimterry

    View Slide

  20. How can I keep
    things out of
    my image?
    @pimterry

    View Slide

  21. FROM node:8
    ARG NPM_TOKEN
    RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \
    npm whoami
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    COPY . .
    RUN npm install && npm run build && rm ~/.npmrc
    CMD [ "/usr/local/bin/node", "./built.js" ]
    @pimterry
    The Problem

    View Slide

  22. FROM node:8
    ARG NPM_TOKEN
    RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \
    npm whoami
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    COPY . .
    RUN npm install && npm run build && rm ~/.npmrc
    CMD [ "/usr/local/bin/node", "./built.js" ]
    @pimterry
    The Problem
    700MB
    image
    (for an empty project)

    View Slide

  23. FROM node:8
    ARG NPM_TOKEN
    RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \
    npm whoami
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    COPY . .
    RUN npm install && npm run build && rm ~/.npmrc
    CMD [ "/usr/local/bin/node", "./built.js" ]
    @pimterry
    The Problem

    View Slide

  24. ➜ docker image inspect 274627c8a3a2
    [
    {
    "Id": "sha256:274627c8a3a28d...",
    ...
    "GraphDriver": {
    "Data": {
    "LowerDir": "...",
    "MergedDir": "/var/lib/docker/overlay2/61038f.../merged",
    "UpperDir": "/var/lib/docker/overlay2/61038f.../diff",
    "WorkDir": "/var/lib/docker/overlay2/61038f.../work"
    },
    },
    }
    ]
    @pimterry
    The Problem

    View Slide

  25. ➜ sudo cat /var/lib/docker/overlay2/61038f.../diff/root/.npmrc
    //registry.npmjs.org/:_authToken=
    @pimterry
    The Problem

    View Slide

  26. ➜ docker history e5936788a37a
    IMAGE CREATED CREATED BY SIZE
    e5936788a37a 2 minutes ago /bin/sh -c #(nop) CMD ["/usr/local/bin/no... 0B
    79fad4492e73 2 minutes ago |1 NPM_TOKEN= ... 537kB
    373ea011abf1 2 minutes ago /bin/sh -c #(nop) COPY dir:6ffc7f0ac3036c2... 459B
    155133064e6a 2 minutes ago /bin/sh -c #(nop) WORKDIR /usr/src/app 0B
    2a5d69935132 2 minutes ago |1 NPM_TOKEN= ... 0B
    274627c8a3a2 2 minutes ago |1 NPM_TOKEN= ... 125B
    8f14bc306cc4 3 minutes ago /bin/sh -c #(nop) ARG NPM_TOKEN 0B
    2eeae8debf3d 2 days ago /bin/sh -c #(nop) CMD ["node"] 0B
    2 days ago /bin/sh -c set -ex && for key in 6A0... 4.17MB
    2 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.3.2 0B
    2 days ago /bin/sh -c ARCH= && dpkgArch="$(dpkg --pri... 56.6MB
    2 days ago /bin/sh -c #(nop) ENV NODE_VERSION=8.9.3 0B
    2 days ago /bin/sh -c set -ex && for key in 94A... 129kB
    ...
    @pimterry
    The Problem

    View Slide

  27. Combine RUN
    commands
    @pimterry

    View Slide

  28. @pimterry
    Combined RUN
    Limitations
    Breaks caching
    Doesn’t help with non-RUN commands
    Doesn’t erase history

    View Slide

  29. docker build
    --squash
    @pimterry

    View Slide

  30. @pimterry
    --squash Limitations
    Requires ‘experimental’ flag
    Silent disaster if you forget to squash
    Can be less efficient
    Doesn’t erase history

    View Slide

  31. Rename previous Dockerfile
    to Dockerfile.build
    docker build . -f Dockerfile.build
    Copy build output from image
    to local file with docker cp
    Build production Dockerfile ⟶
    @pimterry
    Builder Pattern
    Dockerfile:
    FROM node:8-alpine
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    COPY ./built.js .
    CMD [ "/usr/local/bin/node",
    "./built.js" ]

    View Slide

  32. @pimterry
    Builder Pattern
    Limitations
    Pretty complicated
    Needs manual steps/wrapper automation
    Encourages different dev/prod images & workflow

    View Slide

  33. Multi-stage
    Builds
    @pimterry

    View Slide

  34. @pimterry
    Build in stages
    Pull content between stages
    Multi-stage Builds:

    View Slide

  35. FROM base
    repeatedly
    @pimterry

    View Slide

  36. FROM node:latest
    ...
    FROM node:alpine
    ...
    @pimterry

    View Slide

  37. FROM base as first-stage
    ...
    FROM base2
    ...
    FROM first-stage
    ...
    @pimterry

    View Slide

  38. FROM base as first-stage
    ...
    FROM base2
    COPY --from=first-stage /a /b
    @pimterry

    View Slide

  39. Bonus:
    COPY --from=any-image
    @pimterry

    View Slide

  40. History & layers
    for each stage
    are independent
    @pimterry

    View Slide

  41. The last stage is
    the end result
    @pimterry

    View Slide

  42. (Unless it’s not)
    docker build
    --target=
    @pimterry

    View Slide

  43. FROM base as build-stage
    ...
    FROM build-stage as test-stage
    ...
    FROM build-stage
    @pimterry
    Change default target:

    View Slide

  44. Watch out:
    Non-target stages are
    considered dangling
    @pimterry

    View Slide

  45. Watch out:
    --cache-from
    breaks multi-stage caching
    @pimterry

    View Slide

  46. FROM node:8 AS build
    ARG NPM_TOKEN
    RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \
    npm whoami
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    COPY . .
    RUN npm install && npm run build
    CMD [ "/usr/local/bin/node", "./built.js" ]
    FROM node:8-alpine
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    COPY --from=build /usr/src/app/built.js .
    CMD [ "/usr/local/bin/node", "./built.js" ]
    @pimterry
    Fixed Multi-Stage Dockerfile

    View Slide

  47. FROM node:8 AS build
    ARG NPM_TOKEN
    RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc && \
    npm whoami
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    COPY . .
    RUN npm install && npm run build
    CMD [ "/usr/local/bin/node", "./built.js" ]
    FROM node:8-alpine
    RUN mkdir -p /usr/src/app
    WORKDIR /usr/src/app
    COPY --from=build /usr/src/app/built.js .
    CMD [ "/usr/local/bin/node", "./built.js" ]
    @pimterry
    Fixed Multi-Stage Dockerfile
    70MB
    image
    (90% smaller)

    View Slide

  48. IMAGE CREATED CREATED BY SIZE
    0e1999330a57 5 seconds ago /bin/sh -c #(nop) CMD ["/usr/local/bin/no... 0B
    bdd51c6251d5 6 seconds ago /bin/sh -c #(nop) COPY file:19fa00c24fa256... 614kB
    fadd597467a6 6 seconds ago /bin/sh -c #(nop) WORKDIR /usr/src/app 0B
    e7bffff20b93 6 seconds ago /bin/sh -c mkdir -p /usr/src/app 0B
    144aaf4b1367 5 days ago /bin/sh -c #(nop) CMD ["node"] 0B
    5 days ago /bin/sh -c apk add --no-cache --virtual .b... 4.19MB
    5 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.3.2 0B
    5 days ago /bin/sh -c addgroup -g 1000 node && ad... 59.6MB
    5 days ago /bin/sh -c #(nop) ENV NODE_VERSION=8.9.3 0B
    12 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
    12 days ago /bin/sh -c #(nop) ADD file:cb381165dec3689... 3.97MB
    @pimterry
    Hide secure credentials:
    Secrets don’t appear in the history
    Previous stage layers are separate

    View Slide

  49. @pimterry
    Take huge images:
    FROM ekidd/rust-musl-builder
    ADD . ./
    RUN sudo chown -R rust:rust /home/rust
    RUN cargo build --release
    EXPOSE 8000
    CMD ["/home/rust/src/.../rust-server"]
    Resulting image: 2.3 GB
    github.com/pimterry/multistage-demos/tree/master/rust-server

    View Slide

  50. @pimterry
    Build tiny images:
    FROM ekidd/rust-musl-builder as build
    ADD . ./
    RUN sudo chown -R rust:rust /home/rust
    RUN cargo build --release
    FROM scratch
    COPY --from=build
    /home/rust/src/.../rust-server /
    EXPOSE 8000
    CMD ["/rust-server"]
    Resulting image: 4.8 MB
    github.com/pimterry/multistage-demos/tree/master/rust-server

    View Slide

  51. Supported in
    Docker >= 17.05
    @pimterry

    View Slide

  52. Multi-stage builds let you:
    Control image history
    Control image size
    Declaratively describe your build
    @pimterry

    View Slide

  53. Optimizing Docker
    for IoT with
    Multi-Stage
    Builds
    @pimterry

    View Slide