保持软件的更新对于系统的安全性和稳定性至关重大。本文将介绍如何从源代码自助编译 NGINX,并将其集成到 Kubernetes Ingress NGINX Controller 镜像中,以实现 NGINX 的升级。此方法不仅可以提高系统的安全性,还可以定制 NGINX 的功能,以满足特定需求。
前提条件
- 需要一个可以访问 Docker 的环境。
- 需要安装
wget和tar等基本工具。 - 基础镜像使用
quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0。
目标
- 从源代码编译 NGINX。
- 将编译后的 NGINX 集成到 Ingress NGINX Controller 镜像中。
- 保持镜像的最小化和安全性。
官方镜像的 Dockerfile
在开始自定义构建之前,了解官方的 Dockerfile 是很有必要的。以下是 quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0 镜像的官方 Dockerfile:
# Copyright 2015 The Kubernetes Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
ARG TARGETARCH
ARG VERSION
ARG COMMIT_SHA
ARG BUILD_ID=UNSET
LABEL org.opencontainers.image.title="NGINX Ingress Controller for Kubernetes"
LABEL org.opencontainers.image.documentation="https://kubernetes.github.io/ingress-nginx/"
LABEL org.opencontainers.image.source="https://github.com/kubernetes/ingress-nginx"
LABEL org.opencontainers.image.vendor="The Kubernetes Authors"
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL org.opencontainers.image.version="${VERSION}"
LABEL org.opencontainers.image.revision="${COMMIT_SHA}"
LABEL build_id="${BUILD_ID}"
WORKDIR /etc/nginx
RUN apk update
&& apk upgrade
&& apk add --no-cache
diffutils
&& rm -rf /var/cache/apk/*
COPY --chown=www-data:www-data etc /etc
COPY --chown=www-data:www-data bin/${TARGETARCH}/dbg /
COPY --chown=www-data:www-data bin/${TARGETARCH}/nginx-ingress-controller /
COPY --chown=www-data:www-data bin/${TARGETARCH}/wait-shutdown /
# Fix permission during the build to avoid issues at runtime
# with volumes (custom templates)
RUN bash -xeu -c
writeDirs=(
/etc/ingress-controller/ssl
/etc/ingress-controller/auth
/etc/ingress-controller/geoip
/etc/ingress-controller/telemetry
/var/log
/var/log/nginx
/tmp/nginx
);
for dir in "${writeDirs[@]}"; do
mkdir -p ${dir};
chown -R www-data.www-data ${dir};
done
# LD_LIBRARY_PATH does not work so below is needed for opentelemetry/other modules
# Put libs of newer modules under `/modules_mount/etc/nginx/modules/otel` and add that path below
# Could get complicated arch specific paths become a need
&& echo "/lib:/usr/lib:/usr/local/lib:/modules_mount/etc/nginx/modules/otel" > /etc/ld-musl-x86_64.path
RUN apk add --no-cache libcap
&& setcap cap_net_bind_service=+ep /nginx-ingress-controller
&& setcap -v cap_net_bind_service=+ep /nginx-ingress-controller
&& setcap cap_net_bind_service=+ep /usr/local/nginx/sbin/nginx
&& setcap -v cap_net_bind_service=+ep /usr/local/nginx/sbin/nginx
&& setcap cap_net_bind_service=+ep /usr/bin/dumb-init
&& setcap -v cap_net_bind_service=+ep /usr/bin/dumb-init
&& apk del libcap
&& ln -sf /usr/local/nginx/sbin/nginx /usr/bin/nginx
USER www-data
# Create symlinks to redirect nginx logs to stdout and stderr docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log
&& ln -sf /dev/stderr /var/log/nginx/error.log
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/nginx-ingress-controller"]
编译 NGINX
接下来,我们编译 NGINX 需要一个干净的构建环境。在这里,我们使用多阶段构建技术,通过一个临时的构建容器编译 NGINX,并将最终的编译结果复制到目标容器中。
Dockerfile
# 第一阶段:编译 nginx
FROM alpine:3.11 AS build
# 安装编译所需的依赖
RUN apk add --no-cache
build-base
zlib-dev
openssl-dev
pcre-dev
wget
tar
# 下载并解压 Nginx 源代码
ARG NGINX_VERSION=1.26.1
RUN wget https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz -O /tmp/nginx.tar.gz &&
tar -zxvf /tmp/nginx.tar.gz -C /tmp
# 进入源码目录,配置和编译 Nginx
WORKDIR /tmp/nginx-${NGINX_VERSION}
RUN ./configure
--prefix=/usr/local/nginx
--sbin-path=/usr/local/nginx/sbin/nginx
--modules-path=/usr/local/nginx/modules
--conf-path=/etc/nginx/nginx.conf
--error-log-path=/var/log/nginx/error.log
--http-log-path=/var/log/nginx/access.log
--pid-path=/var/run/nginx.pid
--lock-path=/var/run/nginx.lock
--http-client-body-temp-path=/var/cache/nginx/client_temp
--http-proxy-temp-path=/var/cache/nginx/proxy_temp
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp
--http-scgi-temp-path=/var/cache/nginx/scgi_temp
--with-perl_modules_path=/usr/lib/perl5/vendor_perl
--user=nginx
--group=nginx
--with-compat
--with-file-aio
--with-threads
--with-http_addition_module
--with-http_auth_request_module
--with-http_dav_module
--with-http_flv_module
--with-http_gunzip_module
--with-http_gzip_static_module
--with-http_mp4_module
--with-http_random_index_module
--with-http_realip_module
--with-http_secure_link_module
--with-http_slice_module
--with-http_ssl_module
--with-http_stub_status_module
--with-http_sub_module
--with-http_v2_module
--with-mail
--with-mail_ssl_module
--with-stream
--with-stream_realip_module
--with-stream_ssl_module
--with-stream_ssl_preread_module
--with-cc-opt= -Os -fomit-frame-pointer -g
--with-ld-opt=-Wl,--as-needed &&
make && make install
# 第二阶段:创建最终的运行容器
FROM quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0
USER root
# 从构建阶段复制编译好的 Nginx 可执行文件和模块
COPY --from=build /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx
# 创建符号链接
RUN ln -sf /usr/local/nginx/sbin/nginx /usr/bin/nginx
# 确保使用官方的 ENTRYPOINT 和 CMD
USER www-data
# 重新声明 ENTRYPOINT 和 CMD
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/nginx-ingress-controller"]
步骤解释
-
第一阶段:编译 NGINX
- 使用
alpine:3.11作为基础镜像,安装编译 NGINX 所需的依赖。这与quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0的基础镜像保持一致,以确保兼容性。 - 下载并解压 NGINX 源代码。
- 配置并编译 NGINX,指定各类路径和模块。特别注意,
- 使用
-
第二阶段:集成编译好的 NGINX
- 使用
quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0作为基础镜像。 - 将编译好的 NGINX 可执行文件和模块从构建阶段复制到目标镜像中。
- 创建符号链接,以确保 NGINX 可执行文件在系统路径中可用。
- 重新声明
ENTRYPOINT和CMD,确保保持官方镜像的默认配置,这对于在 Kubernetes 中运行超级重大。
- 使用
为什么使用 alpine:3.11 作为编译镜像
选择 alpine:3.11 作为编译镜像的缘由是确保与 quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0 的基础镜像保持一致。这样做有以下几个优点:
- 兼容性:使用一样的基础镜像版本可以最大限度地减少在不同 Alpine 版本之间可能出现的兼容性问题。
- 安全性:使用与官方镜像一样的基础镜像可以确保我们继承了一样的安全性更新和修补程序。
- 一致性:确保编译环境与运行环境的一致性,有助于减少在开发和生产环境中的潜在问题。
官方 Dockerfile 中与 NGINX 相关的代码解释
官方 Dockerfile 中与 NGINX 相关的代码部分主要聚焦在以下几个方面:
-
安装依赖:
RUN apk update && apk upgrade && apk add --no-cache diffutils && rm -rf /var/cache/apk/*这部分代码用于更新软件包列表并安装必要的依赖。
-
复制配置和二进制文件:
COPY --chown=www-data:www-data etc /etc COPY --chown=www-data:www-data bin/${TARGETARCH}/dbg / COPY --chown=www-data:www-data bin/${TARGETARCH}/nginx-ingress-controller / COPY --chown=www-data:www-data bin/${TARGETARCH}/wait-shutdown /这部分代码将配置文件和二进制文件复制到镜像中,并确保它们的所有者是
www-data用户。 -
修复权限:
RUN bash -xeu -c writeDirs=( /etc/ingress-controller/ssl /etc/ingress-controller/auth /etc/ingress-controller/geoip /etc/ingress-controller/telemetry /var/log /var/log/nginx /tmp/nginx ); for dir in "${writeDirs[@]}"; do mkdir -p ${dir}; chown -R www-data.www-data ${dir}; done && echo "/lib:/usr/lib:/usr/local/lib:/modules_mount/etc/nginx/modules/otel" > /etc/ld-musl-x86_64.path这部分代码创建一些必要的目录并设置适当的权限。此外,它还配置了库路径。
-
设置权限:
RUN apk add --no-cache libcap && setcap cap_net_bind_service=+ep /nginx-ingress-controller && setcap -v cap_net_bind_service=+ep /nginx-ingress-controller && setcap cap_net_bind_service=+ep /usr/local/nginx/sbin/nginx && setcap -v cap_net_bind_service=+ep /usr/local/nginx/sbin/nginx && setcap cap_net_bind_service=+ep /usr/bin/dumb-init && setcap -v cap_net_bind_service=+ep /usr/bin/dumb-init && apk del libcap && ln -sf /usr/local/nginx/sbin/nginx /usr/bin/nginx这部分代码为一些二进制文件设置了必要的权限,以允许它们绑定到低编号端口。此外,还创建了符号链接,以确保 NGINX 可执行文件在系统路径中可用。
-
用户和日志:
USER www-data RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log这部分代码切换到
www-data用户,并设置符号链接,以将 NGINX 日志输出到标准输出和标准错误,这对于 Docker 日志收集超级有用。 -
入口点和命令:
ENTRYPOINT ["/usr/bin/dumb-init", "--"] CMD ["/nginx-ingress-controller"]这部分代码定义了容器的入口点和默认命令。
dumb-init用于处理子进程的信号和重新aping,而nginx-ingress-controller是实际的控制器二进制文件。
通过以上步骤,我们不仅实现了从源代码编译 NGINX 并集成到 Ingress NGINX Controller 镜像中,还确保了新镜像与官方镜像在运行时的行为保持一致。这样做不仅提高了系统的安全性和稳定性,还可以根据需要定制 NGINX 的功能。
