Skip to content

Commit a8f3bae

Browse files
angersonewilderj
authored andcommitted
RFC: TensorFlow Dockerfile Assembler (#8)
* Initial version of design * Cleanup proposal * Cleanup ID field * Update info about tags * Update with feedback from flx42 * Note intent of design * Add discussion on alternate use of staged builds * Marked as accepted
1 parent c4faa81 commit a8f3bae

File tree

1 file changed

+259
-0
lines changed

1 file changed

+259
-0
lines changed

rfcs/20180731-dockerfile-assembler.md

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# TensorFlow Dockerfile Assembler
2+
3+
| Status | Accepted |
4+
:-------------- |:---------------------------------------------------- |
5+
| **Author(s)** | Austin Anderson (angerson@google.com) |
6+
| **Sponsor** | Gunhan Gulsoy (gunan@google.com) |
7+
| **Updated** | 2018-08-23 |
8+
9+
10+
# Summary
11+
12+
This document describes a new way to manage TensorFlow's dockerfiles. Instead
13+
of handling complexity via an on-demand build script, Dockerfile maintainers
14+
manage re-usable chunks called partials which are assembled into documented,
15+
standard, committed-to-repo Dockerfiles that don't need extra scripts to build.
16+
It is also decoupled from the system that builds and uploads the Docker images,
17+
which can be safely handled by separate CI scripts.
18+
19+
**Important:** This document is slim. The real meat of the design has already
20+
been implemented in [this PR to
21+
tensorflow/tensorflow](https://github.com/tensorflow/tensorflow/pull/21291).
22+
23+
**Also Important:** This design is not currently attempting to revise the images for speed or size: the design sets out a process that makes optimizing the images much easier to do on a larger scale.
24+
25+
# Background
26+
27+
TensorFlow's Docker offerings have lots of problems that affect both users and
28+
developers. [Our images](https://hub.docker.com/r/tensorflow/tensorflow/) are
29+
not particularly well defined or documented, and [our
30+
Dockerfiles](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/tools/docker)
31+
are complicated and frightening.
32+
33+
## Existing Images are Hard to Optimize
34+
35+
TensorFlow's current set of Dockerfiles are difficult to optimize. Developers
36+
dislike pulling enormous Docker containers, and many of our tags could be
37+
considered clunky (tag sizes yanked from Dockerhub, see also @flx42's comment
38+
on this doc's PR for on-disk sizes):
39+
40+
| Image Tag | Size |
41+
|:-------------------|-------:|
42+
|latest-devel-gpu-py3| 1 GB |
43+
|latest-devel-py3 | 773 MB |
44+
|latest-gpu-py3 |1 GB |
45+
|latest-py3 | 438 MB |
46+
|latest-devel-gpu | 1 GB |
47+
|latest-devel | 727 MB |
48+
|latest-gpu | 1 GB |
49+
|latest | 431 MB |
50+
51+
Including an extra dependency like Jupyter and convenience packagesnot can add
52+
a few hundred megabytes of extra storage. Since some developers want to have
53+
Jupyter in the images and it's too much trouble for us to maintain many similar
54+
Dockerfiles, we've ended up with a limited set of non-optimized images. I'm not
55+
sure if this truly a critical problem, but it's a little annoying (one of my
56+
personal computers only has 32 GB of SSD space on the root drive, and I
57+
regularly need to wipe my docker cache of large images).
58+
59+
## TF Docker Images need Complexity
60+
61+
Our Docker images support two primary use cases: development _with_ TensorFlow,
62+
and development _on_ TensorFlow. We want a matrix of options available for both
63+
of these types of users, the most critical being GPU development support
64+
(currently nvidia-only) and pre-installed Jupyter support. With only those
65+
options considered, we target eight very similar Docker images; sixteen with
66+
Python versioning.
67+
68+
Our current images come from a script called `parameterized_docker_build.sh`,
69+
which live-edits a templated Dockerfile with `sed` to insert new Dockerfile
70+
commands. The script has a poor reputation because it's hard to understand, can
71+
be finicky, and is not easily understood compared to vanilla Dockerfiles. Some
72+
Dockerfiles are duplicated, some are unused, and some users have made their own
73+
instead. None of the Dockerfiles use the ARG directive.
74+
75+
Furthermore, `parameterized_docker_build.sh` is tightly coupled with the
76+
deploy-to-image-hub process we use, which is confusing because users who build
77+
the images locally don't need that information at all.
78+
79+
This document proposes a new way for the TF team to maintain this complex set
80+
of similar Dockerfiles.
81+
82+
# Design
83+
84+
We use a generator to assemble multiple partial Dockerfiles into concrete
85+
Dockerfiles that get committed into source control. These Dockerfiles are fully
86+
documented and support argument customization. Unlike the parameterized image
87+
builder script, this system excludes the image deployment steps, which should
88+
be handled by a totally different system anyway.
89+
90+
This section lightly describes the design, which is fully implemented in [this
91+
pull request to the main TensorFlow
92+
repo](https://github.com/tensorflow/tensorflow/pull/21291).
93+
94+
Partial files are syntactically valid but incomplete files with Dockerfile
95+
syntax.
96+
97+
Assembly is controlled by a specification file, defined in yaml. The spec
98+
defines the partials, the ARGs they use, the list of Dockerfiles to generate
99+
based on ordered lists of partials, and documentation for those values.
100+
101+
The assembler is a python script that accepts a spec and generates a bunch of
102+
Dockerfiles to be committed. The spec includes documentation and descriptions,
103+
and the output Dockerfiles are fully documented and can be built manually.
104+
105+
**Important**: This design in its current implementation does **not** attempt
106+
to address the limitations of our current set of images. Instead, it replicates
107+
the current set of tags with a few easy improvements, the most notable being a
108+
separate set of Dockerfiles that add Jupyter -- identical in every way to the
109+
non-Jupyter images without needing any extra maintenance. This design makes it
110+
much easier to craft TensorFlow's Docker offering in a way that satisfies
111+
everyone with minimal extra work from the Dockerfile maintainers.
112+
113+
# Impact
114+
115+
This approach has many convenient benefits:
116+
117+
* The result is concrete, buildable, documented Dockerfiles. Users who wish
118+
to build their own images locally do not need to also understand the build
119+
system. Furthermore, basing our images on clean Dockerfiles that live in the repository feels clean and right -- as a user, I (personally) like to be able to see how an image works. It removes the mystery and magic from the process.
120+
* This implementation is agnostic to what images we would like to make
121+
available online (i.e. our Docker story). It's very easy to add new dockerfile
122+
outputs.
123+
* The build-test-and-deploy-images process is decoupled from the Dockerfile
124+
generation process.
125+
* Control of the set of dockerfiles is centralized to the spec file, instead
126+
of being spread across each Dockerfile.
127+
* The spec can be extended to add more conveniences. My implementation, for
128+
example, already includes de-duplication of many similar Dockerfile
129+
specifications.
130+
* All dockerfiles are consistently documented.
131+
* Common pieces of code, like a slick shell environment or a Jupyter
132+
interface, can be updated in batch by updating a single partial file.
133+
* The spec can also be used in the image building process, e.g. to read all
134+
available args.
135+
136+
# Caveats and Rejected Alternatives
137+
138+
I considered two alternatives while working on this.
139+
140+
## Hacky Multi-Stage Dockerfile
141+
142+
"Multi-stage Building" is a powerful new Dockerfile feature that supports
143+
multiple FROM statements in one Dockerfile. Multi-stage builds let you build
144+
and run an artifact (like a compiled version of a binary) in any number of
145+
separate stages designated by FROM directives; the resulting image is only as
146+
large as the final stage without the build-only dependencies from previous
147+
stages.
148+
149+
However, Docker's ARG parameter expansion can be used in these extra FROM
150+
directives to conditionally set base images for each build stage:
151+
152+
153+
```dockerfile
154+
# If --build-arg FROM_FOO is set, build FROM foo. else build FROM bar.
155+
ARG FROM_FOO
156+
ARG _HELPER=${FROM_FOO:+foo}
157+
ARG BASE_IMAGE=${_HELPER:-bar}
158+
FROM ${BASE_IMAGE}
159+
160+
```
161+
162+
This means that it's possible to use multi-stage builds and ARGs to create
163+
stages that are conditionally based on previous stages in the Dockerfile.
164+
[This sample
165+
Dockerfile](https://gist.github.com/angersson/3d2b5ae6a01de4064b1c3fe7a56e3821),
166+
which I've included only as a demonstration of a bad idea (and may currently
167+
work), is very powerful but not extensible and not easy to understand. It is
168+
heavily coupled to our current environment, which may change immensely e.g. if
169+
AMD releases Docker images similar to Nvidia's or if someone would like to add
170+
MKL support.
171+
172+
## Multiple Normal Dockerfiles Aggregated into Multiple Stages
173+
174+
In a [comment on this doc's PR](https://github.com/tensorflow/community/pull/8#issuecomment-410080344), @flx42 suggested a much-improved version of the
175+
previous section. Another way of using ARG interpolation in FROM lines would be
176+
to write multiple isolated Dockerfiles that can be layered together during the `docker build` process:
177+
178+
179+
```
180+
ARG from
181+
FROM ${from}
182+
183+
ARG PIP
184+
RUN ${PIP} install jupyter
185+
```
186+
187+
And then:
188+
189+
```
190+
$ docker build -t nvidia-devel -f Dockerfile.nvidia-devel .
191+
$ docker build -t nvidia-devel-jupyter-py3 --build-arg from=nvidia-devel --build-arg pip=pip3 -f Dockerfile.jupyter .
192+
```
193+
194+
This shares the advantage of the current design by working from many reusable parts, but carries some notable tradeoffs:
195+
196+
### Advantages over Current Design
197+
198+
I can see a variety of minor improvements:
199+
200+
- No need for assembler script or spec file
201+
- Possibly faster build times due to concretely isolated image stages
202+
- Image stages (akin to partials) may be more reusable due to slot-like usage of `--build-args`
203+
- Because there are no concrete Dockerfiles, there's only one place that defines the Dockerhub tags and what components describe them (in the current design, the spec files describes the Dockerfiles, and then a little more logic elsewhere in our CI would configure those Dockerfiles with the tags)
204+
205+
### Downsides compared to Current Design
206+
207+
...but some downsides that I think are fairly heavy:
208+
209+
- Spec + Assembler have some very nice advantages (validation, re-use, etc.)
210+
- No concrete Dockerfiles for OSS devs to use / refer to
211+
- Advanced usage requires some unintuitive file/directory layout + build ordering
212+
- Image-building complexity offloaded to OSS developers and to the CI scripts, which would need scripts / logic to define sets of images to build
213+
- Updating requires familiarity with multi-stage behavior
214+
215+
### Conclusion
216+
217+
This is an interesting approach that I like a lot, but I don't think it offers
218+
enough benefits over the current design (which has another advantage in that it
219+
is already mostly finished) to implement.
220+
221+
It's worth noting that using multiple FROM stages is a powerful tool that could
222+
possibly be leveraged in the partials for the current design.
223+
224+
## Manually Maintained Dockerfiles with Script References
225+
226+
Another pattern that supports complicated Dockerfiles is to manually maintain
227+
many Dockerfiles that each call out to a common set of build scripts:
228+
229+
```dockerfile
230+
FROM ubuntu
231+
COPY install_scripts/ /bin
232+
RUN /bin/install_nvidia_dev.sh
233+
RUN /bin/install_python_dev.sh
234+
RUN /bin/install_bazel.sh
235+
...
236+
```
237+
238+
This is better than our current approach, but has many small drawbacks that add
239+
up:
240+
241+
* Argument passing becomes slightly more complex, because ARGs must be passed
242+
and read as either ENV variables or as build arguments.
243+
* Each dockerfile has to be properly documented manually, if at all.
244+
* Developers have to leave the Dockerfile to read the shell scripts, which
245+
gets annoying.
246+
* Maintenance is spread across the dockerfiles and the scripts, and can grow
247+
into even more work (like some Dockerfiles having extra non-script directives,
248+
etc.).
249+
* Extra overhead in the scripts can be kind of wasteful
250+
251+
# Work Estimates
252+
253+
I have already completed a PR that will introduce these Dockerfiles without
254+
affecting our current builds. These would probably take a week or two to
255+
migrate.
256+
257+
## Questions and Discussion Topics
258+
259+
Seed this with open questions you require feedback on from the RFC process.

0 commit comments

Comments
 (0)