为 Kubernetes 构建小型容器
这是一篇转帖。原帖可以在我的博客上找到。
将任何应用部署到Kubernetes 的第一步,都是将应用打包到容器中。市面上有多个官方和社区支持的容器镜像,适用于各种语言和发行版,其中大多数容器可能非常庞大,有时甚至包含您的应用可能永远不需要/用不到的开销。
借助Docker,您只需几个步骤即可轻松创建容器映像;指定基础映像、添加特定于应用程序的更改并构建容器。
FROM golang:alpine
WORKDIR /app
ADD . /app
EXPOSE 8080
ENTRYPOINT ["/app/run"]
我们指定了一个基础镜像(本例中为 Linux alpine),设置了容器中要使用的工作目录,公开了一个网络端口,以及一个用于在容器中启动应用程序的入口点。设置好 Dockerfile 后,我们就可以构建容器了。
$ docker build myapp .
虽然上述过程非常简单,但仍有一些问题需要考虑。使用默认镜像可能会导致容器镜像过大、安全漏洞和内存开销。
让我们充实一个示例应用程序
我们将用 Go 编写一个简单的应用,它暴露一个 HTTP 路由,当路由命中时会返回一个字符串。我们将用它构建一个 Docker 镜像。
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
r := http.NewServeMux()
r.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello From Go!")
})
s := &http.Server{
Addr: ":8080",
Handler: r,
ReadTimeout: 10 * time.Second,
}
fmt.Println("Starting server on port 8080")
log.Fatal(s.ListenAndServe())
}
让我们用我们的应用程序构建 Docker 镜像。首先,我们需要创建一个 Dockerfile。
FROM golang:latest
RUN mkdir /app
ADD . /app/
WORKDIR /app
RUN go build -o myapp .
CMD ["/app/myapp"]
构建图像。
PS:用任何选择的内容替换标签:/appname
$ docker build -t codehakase/goapp .
就这样!我们刚刚 Docker 化了一个简单的 Go 应用。让我们来看看
刚刚构建的镜像。
对于一个简单的 Go 应用来说,镜像文件的大小就超过 700 MB。Go 二进制文件本身
可能只有几 MB,额外的开销不仅浪费了空间,
还可能成为 bug 和安全漏洞的藏身之处。
什么东西占用了这么多空间?在这种情况下,容器需要
安装 Go,以及 Go 所依赖的所有依赖项,所有这些都位于
Debian 或 Linux 发行版之上。
有两种方法可以减少容器镜像的大小,实际上有三种,其中
第三种在 Go 社区中更常用:
- 使用小型基础镜像
- 建造者模式
- 使用空图像
使用小型基础镜像是减少容器镜像大小最简单的方法。
所使用的堆栈/语言可能提供的官方镜像
比默认镜像小得多。
golang:alpine
让我们更新 Dockerfile 以使用一个较小的基础镜像。在本例中我们将使用它。
FROM golang:alpine
RUN mkdir /app
ADD . /app/
WORKDIR /app
RUN go build -o myapp .
CMD ["/app/myapp"]
重建图像。
$ docker build -t codehakase/goapp .
随着 Dockerfile 的更新,我们的镜像现在比
以前的镜像更小了。
这个镜像大小仍然很大,我们可以使用
建造者模式将其缩小。由于我们在本例中使用了编译型语言(Go),因此在
建造者模式中,我们应该注意,编译型语言通常需要一些
并非运行代码所必需的工具。这些工具主要用于构建和编译为二进制文件。使用建造者模式,我们可以 在最终容器中
移除这些工具。
为了在我们现有的示例中使用 Builder 模式,我们将在
容器中编译我们的代码,然后使用编译后的代码打包最终的容器,而无需
所有必需的工具。
FROM golang:alpine AS main-env
RUN mkdir /app
ADD . /app/
WORKDIR /app
RUN cd /app && go build -o myapp
FROM alpine
WORKDIR /app
COPY --from=main-env /app/myapp /app
EXPOSE 8080
ENTRYPOINT ./myapp
我们更新了 Dockerfile 以使用构建器模式。首先,它在容器
中构建并编译应用程序alpine
并命名为步骤main-env
,然后
将上一步中的二进制文件复制到新容器中。
重建多阶段 Dockerfile。
$ docker build -t codehakase/goapp .
还记得我们构建的第一个镜像超过 700 MB 吗?我们
使用构建器模式将其缩减到 10.7 MB。
我们仍然可以通过使用临时(空)
镜像来稍微减少这个数字。什么是临时镜像?它是一个特殊的空 Docker 镜像。要使用
它,我们需要首先在 Docker 环境之外构建我们的应用程序,并将编译后的二进制文件添加
到容器中。
$ go build -o myapp .
我们将更新 Dockerfile 以将二进制文件添加到临时映像。
FROM scratch
ADD myapp /
CMD ["/myapp"]
我们把它压缩到了 6.5MB,太棒了!让我们尝试运行容器来测试
我们的应用。
$ docker run -it codehakase/goapp
出现此错误的原因是,Go 二进制文件正在其运行的操作系统上寻找库
,由于临时映像为空,因此没有可供查找的库。
我们需要修改构建命令以静态编译我们的 Go 应用程序:
$ GO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o myapp .
使用重建docker build -t codehakase/goapp
,并再次运行我们的容器
,将容器上的端口转发到我们的机器:
$ docker run -it -p 8080:8080 codehakase/goapp
Starting server on port 8080
导航至以http://localhost:8080/api
测试应用程序的响应。
结论
本文旨在解释如何
专门针对 Go 应用缩减容器大小。容器越小,性能就越高,因为
在 CI 环境中构建容器的速度会更快,将
构建好的镜像推送到容器注册表所需的时间也会更少,
最重要的是,将这些容器拉取到分布式 Kubernetes
集群的速度也会更快,因为较小的容器不太可能延迟
新集群的部署。
如果您有任何建议或意见,请在下方留言或联系
@codehakase