💨

freenginxをGitLab CI/CDでビルドしました - 最新バージョン自動ビルド

2024/04/06に公開
1

freenginx

announcing freenginx.org

https://freenginx.org/

以前の投稿でmercurialのfreenginxレポジトリより1.25.3をビルドしましたが、
今週2024/4/2にfreenginx 1.25.5が来ていたので、今度はそれをもとにGitLab CI/CD + kanikoでビルドしました。

  • freenginx:1.25.5
  • alpineベース (3.19.1)
  • self-managed GitLab (and GitLab Runner for CI/CD)
  • kanikoのexecutor:v1.20.1-debugでイメージビルド
  • self-managed Harborへビルドイメージ格納、proxy cache利用

20240410追記: もうfreenginx:1.26.0が登場していました。本ポストの最後に最新バージョンのalpineとfreenginxを確認し、ビルドする微改変版を最後に追加しました。

レポジトリの中身

閉じた環境、そしてプライベートのレポジトリでやっておりますので、レポジトリ共有する代わりに中身を紹介します。

freenginxビルド用に設けたレポジトリに用意したファイルは次の通りです。

.
 |-.gitlab-ci.yml
 |-alpine
 | |-Dockerfile
 |-docker-entrypoint.sh
 |-10-listen-on-ipv6-by-default.sh
 |-20-envsubst-on-templates.sh
 |-30-tune-worker-processes.sh
 |-README.md
 |-.git

./docker-entrypoint.shなどの./*.shファイルはhttps://github.com/nginxinc/docker-nginxより拝借しています。

./alpine/DockerfileはalpineイメージをベースにfreenginxをビルドするためのDockerfileです。もしかすると今後./{other dist}/Dockerfileなども用意するかもしれません。

./.gitlab-ci.ymlはGitLab CI/CDでGitLab Runnerに実行してもらう内容です。

Dockerfile

ベースイメージはproxy越しにpullするようにしています。

freenginxのレポジトリのタグは./.gitlab-ci.ymlより渡しています。

ただ、ベースイメージのタグはハードコードしていますし、
ビルドを回す条件がDockerfile変更時としているため、
freenginxのタグもDockerfile内にハードコードして都度更新でよいかと考えています。

./alpine/Dockerfile
# base alpine image
# https://hub.docker.com/_/alpine
ARG HARBOR_HOST
ARG FREENGINX_TAG
FROM $HARBOR_HOST/cache-dockerhub/library/alpine:3.19.1

# user
RUN addgroup -g 101 -S nginx \
  && adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx

# build deps
RUN apk add --no-cache pcre2-dev tzdata gettext
RUN apk add --no-cache --virtual .build-deps \
  gcc \
  libc-dev \
  make \
  openssl-dev \
  zlib-dev \
  linux-headers \
  bash \
  alpine-sdk \
  findutils \
  mercurial

# hg repo
WORKDIR /tmp
RUN hg clone https://freenginx.org/hg/nginx
WORKDIR /tmp/nginx
RUN hg update $FREENGINX_TAG \
  && auto/configure \
  --prefix=/etc/nginx \
  --sbin-path=/usr/sbin/nginx \
  --modules-path=/usr/lib/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 \
  --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-http_v3_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='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' \
  --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' \
  && make \
  && make install

# remove build deps
WORKDIR /
RUN apk del .build-deps && rm -rf /tmp/nginx

# directories and files
RUN mkdir -p /var/cache/nginx/client_temp \
    /var/cache/nginx/proxy_temp \
    /var/cache/nginx/fastcgi_temp \
    /var/cache/nginx/uwsgi_temp \
    /var/cache/nginx/scgi_temp \
    /docker-entrypoint.d \
  && ln -sf /dev/stdout /var/log/nginx/access.log \
  && ln -sf /dev/stderr /var/log/nginx/error.log

COPY docker-entrypoint.sh /docker-entrypoint.sh
COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d
COPY 20-envsubst-on-templates.sh /docker-entrypoint.d
COPY 30-tune-worker-processes.sh /docker-entrypoint.d

ENTRYPOINT ["/docker-entrypoint.sh"]
EXPOSE 80
STOPSIGNAL SIGQUIT
CMD ["nginx", "-g", "daemon off;"]

.gitlab-ci.yml

kanikogcr.io/kaniko-project/executorにイメージがあるのですが
Dockerfile内、ベースイメージ同様、自前のHarborで設定しているproxyを利用してpullしています。

またGitLabのintegrationsでHarborをセットアップしているので、その変数も用いています。

After the Harbor integration is activated, global variables $HARBOR_USERNAME, $HARBOR_HOST, $HARBOR_OCI, $HARBOR_PASSWORD, $HARBOR_URL and $HARBOR_PROJECT will be created for CI/CD use.

https://docs.gitlab.com/ee/user/project/integrations/harbor.html

./alpine/Dockerfileに変更があった場合には"build-test"ジョブでビルドするだけ、レポジトリでtagを付けた時はビルドおよびHarborへのプッシュをするようにしています。こういった条件もありますし、先述の通りfreenginxのタグに関してはここから渡すのではなく、Dockerfile内に記載して都度更新するよう変更しようかと考えています。

./.gitlab-ci.yml
variables:
  KANIKO_CACHE_DIR: "/cache"
  FREENGINX_TAG: "release-1.25.5"

stages:
  - build

build-test:
  stage: build
  image:
    name: $HARBOR_HOST/cache-gcrio/kaniko-project/executor:v1.20.1-debug
    entrypoint: [""]
  script:
    - echo "{\"auths\":{\"$HARBOR_HOST\":{\"auth\":\"$(echo -n ${HARBOR_USERNAME}:${HARBOR_PASSWORD} | base64)\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor
      --build-arg "HARBOR_HOST=$HARBOR_HOST"
      --build-arg "FREENGINX_TAG=$FREENGINX_TAG"
      --cache
      --cache-dir /cache
      --cache-copy-layers
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/alpine/Dockerfile"
      --no-push
      --cache-repo "${HARBOR_HOST}/${HARBOR_PROJECT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}"
  rules:
    - changes:
        - alpine/Dockerfile
    - if: $CI_COMMIT_TAG
      when: never

build:
  stage: build
  image:
    name: $HARBOR_HOST/cache-gcrio/kaniko-project/executor:v1.20.1-debug
    entrypoint: [""]
  script:
    - echo "{\"auths\":{\"$HARBOR_HOST\":{\"auth\":\"$(echo -n ${HARBOR_USERNAME}:${HARBOR_PASSWORD} | base64)\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor
      --build-arg "HARBOR_HOST=$HARBOR_HOST"
      --build-arg "FREENGINX_TAG=$FREENGINX_TAG"
      --cache
      --cache-dir /cache
      --cache-copy-layers
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/alpine/Dockerfile"
      --destination "${HARBOR_HOST}/${HARBOR_PROJECT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${CI_COMMIT_TAG}"
  only:
    - tags
  retry: 2

イメージ完成

Dockerで動かしているイメージをビルドしたもので差し替えてみると、無事動いてくれました。nginx -Vは以下の通りです。

streamでのtls pass through、tls offloadしての他にDockerで動かしているサービスへのリバースプロキシ、AutheliaにredirectしてのMFAなど、うちの環境で動いているものはすべて問題なさそうでなによりです。

$ docker exec nginx nginx -V
nginx version: freenginx/1.25.5
built by gcc 13.2.1 20231014 (Alpine 13.2.1_git20231014)
built with OpenSSL 3.1.4 24 Oct 2023
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/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 --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-http_v3_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='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'

Harbor上でイメージの脆弱性スキャンをしてくれているTrivyによると該当するものはないようです。2月にDebianのベースイメージでビルドした分では600+のレポートが出てくるので、怖いくらいすっきりしています。

おわりに

先日Harborのメンテナンスをした際に、私が利用させていただいている基本イメージあれこれの脆弱性スキャン結果を見たところ、
alpineは目立って問題が少なかったので他のイメージも試しに作ってみたいと考えていました。

メインのウェブサーバとして利用しているnginx/freenginxも更新したかったのでよい機会でした。

ところでnginxは投稿時点で2月の1.25.4更新が最新でした。今後それぞれの開発がどうなっていくのか気になります。

freenginx - CHANGE

nginx - CHANGE

追記 - 最新バージョン自動ビルドGitLab CI/CD

GitLabのCI/CDはスケジュールに応じて勝手に回すようにもできます。

https://docs.gitlab.com/ee/ci/pipelines/schedules.html

スケジュール機能で定期的に最新のイメージをビルドしてregistryにpushするよう、以下のように./.gitlab-ci.yml./alpine/Dockerfileを改変しました。

.gitlab-ci.yml

  • latest-tagというjobで、freenginxのレポジトリより最新タグ取得およびdocker hubよりalpineの最新タグ取得
  • build-scheduledというjobでそれらのタグを用いてビルドおよびプッシュ
./.gitlab-ci.yml
variables:
  KANIKO_CACHE_DIR: "/cache"

stages:
  - prepare
  - build

latest-tag:
  stage: prepare
  image: $HARBOR_HOST/cache-dockerhub/library/golang:1.22.2
  before_script:
    - git --version || apt update && apt install git
    - hg --version || apt install mercurial
  script:
    - git clone https://github.com/pkkudo/taglist && cd taglist && go build
    - ./taglist --help
    - ALPINE_TAG=$(./taglist -repo alpine)
    - echo ALPINE_TAG=$ALPINE_TAG > ${CI_PROJECT_DIR}/.env
    - cd ${CI_PROJECT_DIR} && hg clone https://freenginx.org/hg/nginx && cd nginx
    - FREENGINX_TAG=$(hg tags --pager false | awk 'NR == 2 {print $1}')
    - FREENGINX_VER=$(echo $FREENGINX_TAG | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+')
    - echo FREENGINX_TAG=$FREENGINX_TAG >> ${CI_PROJECT_DIR}/.env
    - echo FREENGINX_VER=$FREENGINX_VER >> ${CI_PROJECT_DIR}/.env
  artifacts:
    reports:
      dotenv: ${CI_PROJECT_DIR}/.env

### omitting the other build jobs ###

build-scheduled:
  stage: build
  image:
    name: $HARBOR_HOST/cache-gcrio/kaniko-project/executor:v1.20.1-debug
    entrypoint: [""]
  script:
    - echo "{\"auths\":{\"$HARBOR_HOST\":{\"auth\":\"$(echo -n ${HARBOR_USERNAME}:${HARBOR_PASSWORD} | base64)\"}}}" > /kaniko/.docker/config.json
    - /kaniko/executor
      --build-arg "HARBOR_HOST=$HARBOR_HOST"
      --build-arg "FREENGINX_TAG=$FREENGINX_TAG"
      --build-arg "ALPINE_TAG=$ALPINE_TAG"
      --cache
      --cache-dir /cache
      --cache-copy-layers
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/alpine/Dockerfile"
      --destination "${HARBOR_HOST}/${HARBOR_PROJECT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${FREENGINX_VER}"
      --destination "${HARBOR_HOST}/${HARBOR_PROJECT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:latest"
      --destination "${HARBOR_HOST}/${HARBOR_PROJECT}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}:${FREENGINX_VER}-${ALPINE_TAG}"
  retry: 2
  needs:
    - job: latest-tag
      artifacts: true
  rules:
    - if: $CI_PIPELINE_SOURCE == 'schedule'

Dockerfile

  • ARG ALPINE_TAGを追加し、FROMで利用
./alpine/Dockerfile
#syntax=docker/dockerfile:1
# base alpine image
# https://hub.docker.com/_/alpine
ARG HARBOR_HOST
ARG FREENGINX_TAG
ARG ALPINE_TAG
FROM $HARBOR_HOST/cache-dockerhub/library/alpine:$ALPINE_TAG

### no changes in the rest

追記以上です。

Discussion

おしょうさんおしょうさん
  • successfully built freenginx v1.27.0
  • works as nginx container for nginx gateway fabric
    • added libxml2-dev & libxslt-dev
    • cloned and compiled njs
#syntax=docker/dockerfile:1
# base alpine image
# https://hub.docker.com/_/alpine
ARG HARBOR_HOST
ARG FREENGINX_TAG
ARG ALPINE_TAG
FROM $HARBOR_HOST/cache-dockerhub/library/alpine:$ALPINE_TAG

# dns
RUN conf=$(sed "s/^options ndots/#options ndots/" /etc/resolv.conf) && echo "$conf" > /etc/resolv.conf && echo "options ndots:0" >> /etc/resolv.conf
RUN cat /etc/resolv.conf

# user
RUN addgroup -g 101 -S nginx \
  && adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx

# build deps
RUN apk add --no-cache pcre2-dev tzdata gettext
RUN apk add --no-cache --virtual .build-deps \
  gcc \
  libc-dev \
  make \
  openssl-dev \
  zlib-dev \
  linux-headers \
  bash \
  alpine-sdk \
  findutils \
  libxml2-dev \
  libxslt-dev \
  mercurial

# hg repo
WORKDIR /tmp
RUN hg clone http://hg.nginx.org/njs
WORKDIR /tmp/njs
RUN ./configure && make njs && cp build/njs /usr/local/bin
WORKDIR /tmp
RUN hg clone http://freenginx.org/hg/nginx
WORKDIR /tmp/nginx
RUN hg update $FREENGINX_TAG \
  && auto/configure \
  --prefix=/etc/nginx \
  --sbin-path=/usr/sbin/nginx \
  --modules-path=/usr/lib/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 \
  --add-dynamic-module=../njs/nginx \
  --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 \
  --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-http_v3_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='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' \
  --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' \
  && make \
  && make install

# remove build deps
WORKDIR /
RUN apk del .build-deps && rm -rf /tmp/nginx

# directories and files
RUN mkdir -p /var/cache/nginx/client_temp \
    /var/cache/nginx/proxy_temp \
    /var/cache/nginx/fastcgi_temp \
    /var/cache/nginx/uwsgi_temp \
    /var/cache/nginx/scgi_temp \
    /docker-entrypoint.d \
  && ln -sf /dev/stdout /var/log/nginx/access.log \
  && ln -sf /dev/stderr /var/log/nginx/error.log

COPY docker-entrypoint.sh /docker-entrypoint.sh
COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d
COPY 20-envsubst-on-templates.sh /docker-entrypoint.d
COPY 30-tune-worker-processes.sh /docker-entrypoint.d

ENTRYPOINT ["/docker-entrypoint.sh"]
EXPOSE 80
STOPSIGNAL SIGQUIT
CMD ["nginx", "-g", "daemon off;"]