If you are compiling go binaries then just use scratch for the absolute smallest docker image possible.

xuanbao · · 466 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>If you don&#39;t use TLS you don&#39;t even need the certs. Nothing will be small or have less attack surface.</p> <p>---- Dockerfile ----</p> <pre><code># Multi-stage builds make it easier to borrow automations and # artifacts from other images. The example below uses the alpine # package manager to fetch the current ca-certificates package # and later copy the downloaded artifact into a different image # based on scratch. FROM alpine:latest as builder RUN apk --update add ca-certificates # Build our binary RUN CGO_ENABLED=0 go get -a -ldflags &#39;-s&#39; github.com/dstroot/helloworld # Build the final container. Use scratch as the smallest possible container FROM scratch # Add in certs COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # Add the binary COPY --from=builder /go/bin/helloworld . # Run it EXPOSE 8000 CMD [&#34;./helloworld&#34;] </code></pre> <hr/>**评论:**<br/><br/>dsies1: <pre><p>I would advise most to not use scratch. While the idea is nice, at some point you will need to ‘exec -it your-container /bin/sh’ even for a curl.</p> <p>I would suggest to use an alpine image. It is still incredibly small and you still have a pkg manager for that one time you need to run tcpdump to see what’s going on with dns or whatever.</p> <p>... And the certificate thing has bit me too many times... I could’ve sworn there were more potential issues, but I can’t think of them off the top of my head.</p></pre>titpetric: <pre><p>I came here for this comment. A common issue is the lack of system locales if you&#39;re doing any kind of locale-based date/time/message formatting, timezone information. These are more functionality based caveats, unlike what you suggest - tooling to inspect and debug possibly misbehaving apps in the container. I opt to keep alpine myself, even if none of that is installed at least I can install it when the container is running, but that does violate the pets vs. cattle argument because you really shouldn&#39;t be dealing with containers in that way but instead should level up on your logging/instrumentation code so you won&#39;t need to (exec into your container(s)).</p></pre>eattherichnow: <pre><blockquote> <p>While the idea is nice, at some point you will need to ‘exec -it your-container /bin/sh’ even for a curl.</p> </blockquote> <p>No you won&#39;t:</p> <pre><code>docker run --net=container:thingie --pid=container:thingie -it --rm busybox sh </code></pre> <p>You might want to check out cap-add to enable things like strace. Here&#39;s a <a href="https://medium.com/@rothgar/how-to-debug-a-running-docker-container-from-a-separate-container-983f11740dc6">sample</a> article someone wrote about this, and for a wider background, <a href="https://blog.jessfraz.com/post/containers-zones-jails-vms/">this</a> is a good start IMO.</p></pre>dazzford: <pre><p>While alpine is small, it&#39;s still unnecessary in a production environment. </p> <p>Deploy an alpine image only as a temporary debugging tool, and only if absolutely necessary. Once &#34;resolved&#34;, remove the alpine container.</p> <p>I have run many dozens of go containers over the past 3 years in production and have yet found the need to run anything other than scratch.</p></pre>touristtam: <pre><p>Erm .... Why do I need docker now to compile the go binaries? Are you implying that compiling locally either from a Windows 10 or a Ubuntu 17.10 machine will produce bigger binaries for the program written in Go?</p></pre>debee1jp: <pre><p>I think the reason is to ensure your build has parity across dev/test/prod. </p></pre>touristtam: <pre><p>Duh, that makes sense. :)</p></pre>debee1jp: <pre><p>Yeah, it makes sense. I think I prefer building through a CI/CD pipeline and shipping the artifact (binary) off somewhere. But you could probably have both just in case.</p></pre>eattherichnow: <pre><p>Yeah, I mean, I usually keep the entire toolkit on my laptop, mostly to keep code completion fed and happy - but the actual building happens in a container, making (almost) sure that things will work the same in CI and production.</p></pre>feketegy: <pre><p>And concatenate commands in your dockerfile because of how docker stages and caches these executions can result in incredibly large caches.</p> <p><a href="https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers" rel="nofollow">https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#minimize-the-number-of-layers</a></p> <p>UPDATE: I see that docker v17.x supports multi-stage builds which could minimize the cache sizes.</p></pre>: <pre><p>[deleted]</p></pre>titpetric: <pre><p>the universe collapses unto itself causing a mega singularity which explodes in a massive supernova and expansion of the known universe begins again and we can have the same discussion in a couple of trillion of years</p></pre>earthboundkid: <pre><p>What are the implications of using Go for process 0?</p></pre>tv64738: <pre><p>You mean pid 1.</p> <p>It will receive SIGCHLD of dying children whose parents are dead, and should wait on them to reap them. If you don&#39;t use subprocesses, that means pretty much nothing. Even if you do, it largely doesn&#39;t. Even if it does, it&#39;s an easy fix.</p></pre>semi-: <pre><p>There is an init system named tini you can use to launch your app. It&#39;s built into docker now(--init) but I still like bundling it in the docker files to work in places that can&#39;t specify docker arts.</p></pre>titpetric: <pre><p>You can handle SIGTERM in your Go app and you don&#39;t need tini. Even if you have a multi-process container, you can use systemd directly which will handle SIGTERM, and I&#39;m not sure adding tini to save ~5 lines of code is anyhow worth it. I have no idea what kind of shit people put into containers apparently, since even PHP can register a signal handler to catch SIGTERM or whatever, and if it&#39;s running on pid 1 there&#39;s <em>no need to forward any signals to it because it already gets all the signals</em>.</p> <p>/rant over</p></pre>dsies1: <pre><p>I would prefer to run it as 0 rather than not - path of least surprises. The “oh it can’t write logs because supervisor is doing something funky” or some silly issue with nginx - can be entirely avoided.</p> <p>As for attack surface - sticking a rate limiter or a cidr limiter in a middleware is simple enough.</p> <p>Plus noisy neighbor and all that jazz.</p></pre>johandryamador: <pre><p>Sometimes you need it from Scratch, others from Alpine. You can make it even smaller using <strong>upx</strong>. Here (<a href="http://blog.johandry.com/post/intro-microservice-in-go-1/#build-and-ship-it" rel="nofollow">http://blog.johandry.com/post/intro-microservice-in-go-1/#build-and-ship-it</a>) is how to go from a Ubuntu based image of 718MB to a <strong>1.26MB</strong> image based on Scratch, using the ldflags &#34;-s -w&#34; and UPX compression. Of course, the more small it is, the most difficult is to debug or has impact to performance. IMO I&#39;d start with Alpine then reduce it depending of the use it will have.</p></pre>captncraig: <pre><p>Fun story about upx: I started using it to compress binaries that we were including in a git repo (don&#39;t ask, is made sense at the time). Turns out compressing the binary made it harder for git to pack it into the repo, and my repository size <em>increased</em> as a result of doing my own binary compression. I&#39;m not sure what my point is in this context, other than &#34;are you sure it is worth it for a marginal gain that may or may not be mitigated in other ways?&#34;</p></pre>johandry_amador: <pre><p>Wow, thanks for sharing, it makes sense and I never thought about it in that way. I think you still can use <code>upx</code> for containers, considering the binary (container or go binary) won&#39;t be pushed to GitHub. Your makefile can build the container and push it to Docker Hub.</p></pre>wavy_lines: <pre><p>Why would anyone want to put a go binary in a docker container? It&#39;s self contained already!</p></pre>johandryamador: <pre><p>i.e. Microservice</p></pre>wavy_lines: <pre><p>I&#39;m afraid you&#39;d have to elaborate (assuming your comment intends to be informative at all?)</p></pre>johandry_amador: <pre><p>If you are running a monolithic application you don&#39;t need a container but if you are building microservices you need to build them in containers. Then push them in a orchestrator like Kubernetes, Docker Compose, RancherOS, OpenShift, ... You can start developing/building the microservice in your computer (not in a container) but eventually it has to go in a container. Some pages that may be useful: <a href="https://nordicapis.com/writing-microservices-in-go/" rel="nofollow">https://nordicapis.com/writing-microservices-in-go/</a> <a href="http://callistaenterprise.se/blogg/teknik/2017/02/17/go-blog-series-part1/" rel="nofollow">http://callistaenterprise.se/blogg/teknik/2017/02/17/go-blog-series-part1/</a> And I recommend this book: <a href="https://www.amazon.com/Building-Microservices-Go-Nic-Jackson/dp/1786468662" rel="nofollow">https://www.amazon.com/Building-Microservices-Go-Nic-Jackson/dp/1786468662</a></p></pre>captncraig: <pre><p>A few possibilities:</p> <ul> <li>You have infrastructure already in place for deploying docker containers regardless of the underlying technology.</li> <li>Docker is generally easier for some people. Running multiple instances of things without making systemd unit files for each one can be appealing. Mapping network ports, service registration/discovery, and monitoring, are sometimes nicer with the docker management layer coordinating things.</li> <li>Building in a docker container removes need to install go directly on build server. This allows different applications to choose which version of the go compiler they target, and there is no worry of conflict / upgrade timing issues, etc.</li> </ul> <p>I think you kinda hit on a big point this kind of article makes though. We don&#39;t need to build very much on top of a go binary at all. There is no point in adding all of the OS libs and things, since the binary is (mostly) static. </p> <p>I see a scratch image like this mostly as an adapter shim to let you use go binaries with other docker infrastructure.</p></pre>SeerUD: <pre><p>That Dockerfile won&#39;t work, Go isn&#39;t available in the Alpine image by default, so the build wouldn&#39;t even occur. You&#39;d have to build in a Go image still, or install Go in the Alpine image first at least.</p></pre>XANi_: <pre><p>Surely the empty image would be smallest possible ?</p> <p>EDIT: I mean &#34;just empty image with golang statically compiled binary&#34;</p></pre>titpetric: <pre><p>Technically you need to run something for it to be valid. So, I think the physical limit for ELF binaries will be <a href="http://www.muppetlabs.com/%7Ebreadbox/software/tiny/teensy.html" rel="nofollow">45 bytes</a> unless there&#39;s some more magic to be used. Sorry for the spoiler, but give it a read if you can, it&#39;s magnificent.</p></pre>XANi_: <pre><p>Well yeah, the go app you are running.</p> <p>I&#39;m just saying that go image doesn&#39;t really need anything else. So all you need is basically empty dir, maybe a tmp/dev/proc but that&#39;s it, no actual files.</p></pre>titpetric: <pre><p>If you&#39;re writing a hello world, sure :) that&#39;s why op adds TLS because they are a requirement if you want to do https API calls for example. The image he uses (<code>scratch</code>) is basically 0 bytes / empty.</p></pre>XANi_: <pre><p>Ah sorry, I&#39;ve misread the dockerfile, thought he was building it from alpine</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

466 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传