容器化 | 使用 Alpine 构建 Redis 镜像

上一期我们介绍了几种常见的构建镜像方式,并给出了功能对比、决策树等作为选型参考。本期我们将演示如何使用 Alpine 构建一个 Redis 镜像。

Alpine 系统使用 apk 包管理工具,文中相关 apk 使用技巧不再赘述。
我们将构建镜像 Dockerfile 中的几个部分单独讲解,最后提供一个完整的 Demo。重点在如何使用 Alpine,Redis 镜像构建步骤此文不详细介绍。

文章大纲:

首先,为了在本地更流畅的部署,可以更换更流畅的镜像源。我们这里将 apk 的软件包镜像源修改为中科大镜像源。

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

构建镜像时,只一些只有构建时才需要的文件/软件。比如 gcc 只有编译时候才会用到;wget 用于下载文件;传统 yum/apt 需要用完后手动卸载软件和依赖,还要自己清理临时文件。而 Alpine 的 apk 可以用起来更方便。

  • –no-cache:不使用缓存目录缓存,装完也不保留缓存。
  • –virtual .build-deps:不真实安装,将软件安装到虚拟包 .build-deps 中,方便 del 时一口气卸载所有包。
  • –no-network:不使用网络。
RUN set -eux; \
    \
    apk add --no-cache --virtual .build-deps \
        coreutils \
        wget \
        dpkg-dev dpkg \
        gcc \
        linux-headers \
        make \
        musl-dev \
        openssl-dev ;\
    #########################################
    #
    #  构建部分
    #
    #########################################
    apk del --no-network .build-deps;

通常我们启动镜像前需要使用 shell 调整下启动状态,做准备。比如:生成配置文件,检查路径挂载等等。然后再拉起服务,但会遇到两个问题:

解决方案

说明: 这里使用 su-exec 而不使用 sudo 的原因是传统 sudo 会新创建出一个进程运行服务,导致 PID 不能为 1,进而导致无法接收到 signal 信号而正常关闭服务。

Dockerfile 内容如下:

FROM alpine:3.16
创建用户
RUN addgroup -S -g 1000 redis && adduser -S -G redis -u 999 redis
安装 su-exec 命令
RUN apk add --no-cache 'su-exec>=0.2'

############################################################
#
其它部署 内容
#
############################################################

ENTRYPOINT ["/usr/bin/entrypoint.sh"]

/usr/bin/entrypoint.sh 内容如下:

#!/bin/sh
set -e

############################################################
#
启动准备脚本内容
#
############################################################

最终启动命令
exec su-exec redis redis-server "$@"

由于 Alpine 系统的时区是 UTC,作者需要设置为上海。可以用如下方式调整时区。

RUN apk add --no-cache tzdata ;\
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

如果对镜像简化要求较高,可以在调整时区后删除时区包,这会给以后修改时区带来困难。

[En]

If there is a high requirement for image simplification, you can delete the time zone package after adjusting the time zone, which will make it difficult to modify the time zone in the future.

RUN apk add --no-cache --virtual .build-tzdata tzdata ; \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\
    apk del .build-tzdata;

我们将以上部分整理为一个完整的 Redis 镜像构建 Demo (Redis 版本 6.2.7)。

Dockerfile 文件

FROM alpine:3.16

RUN addgroup -S -g 1000 redis && adduser -S -G redis -u 999 redis

改为中科大镜像源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

调整时区
RUN apk add --no-cache --virtual .build-tzdata tzdata ; \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime ;\
    apk del .build-tzdata;

RUN apk add --no-cache \
                'su-exec>=0.2' \
                tzdata

ENV REDIS_VERSION 6.2.7
ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-6.2.7.tar.gz
ENV REDIS_DOWNLOAD_SHA b7a79cc3b46d3c6eb52fa37dde34a4a60824079ebdfb3abfbbfa035947c55319

编译 Redis 相关镜像
RUN set -eux; \
        \
        # 使用 --virtual 将软件包虚拟安装到 .build-deps 空间下, 之后会被打包
        # 使用 --no-cache 不会留下缓存文件
        apk add --no-cache --virtual .build-deps \
                coreutils \
                dpkg-dev dpkg \
                gcc \
                linux-headers \
                make \
                musl-dev \
                openssl-dev \
                wget \
        ; \
        \
        wget -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \
        echo "$REDIS_DOWNLOAD_SHA *redis.tar.gz" | sha256sum -c -; \
        mkdir -p /usr/src/redis; \
        tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \
        rm redis.tar.gz; \
        \
        # 禁用 Redis 的保护模式
        grep -E '^ *createBoolConfig[(]"protected-mode",.*, *1 *,.*[)],$' /usr/src/redis/src/config.c; \
        sed -ri 's!^( *createBoolConfig[(]"protected-mode",.*, *)1( *,.*[)],)$!\10\2!' /usr/src/redis/src/config.c; \
        grep -E '^ *createBoolConfig[(]"protected-mode",.*, *0 *,.*[)],$' /usr/src/redis/src/config.c; \
        \
        # 获取CPU架构
        gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \
        extraJemallocConfigureFlags="--build=$gnuArch"; \
        dpkgArch="$(dpkg --print-architecture)"; \
        case "${dpkgArch##*-}" in \
                amd64 | i386 | x32) extraJemallocConfigureFlags="$extraJemallocConfigureFlags --with-lg-page=12" ;; \
                *) extraJemallocConfigureFlags="$extraJemallocConfigureFlags --with-lg-page=16" ;; \
        esac; \
        extraJemallocConfigureFlags="$extraJemallocConfigureFlags --with-lg-hugepage=21"; \
        # 根据架构调整 Makefile 文件
        grep -F 'cd jemalloc && ./configure ' /usr/src/redis/deps/Makefile; \
        sed -ri 's!cd jemalloc && ./configure !&'"$extraJemallocConfigureFlags"' !' /usr/src/redis/deps/Makefile; \
        grep -F "cd jemalloc && ./configure $extraJemallocConfigureFlags " /usr/src/redis/deps/Makefile; \
        \
        # 编译 Redis
        export BUILD_TLS=yes; \
        make -C /usr/src/redis -j "$(nproc)" all; \
        make -C /usr/src/redis install; \
        \
        # Redis
        serverMd5="$(md5sum /usr/local/bin/redis-server | cut -d' ' -f1)"; export serverMd5; \
        find /usr/local/bin/redis* -maxdepth 0 \
                -type f -not -name redis-server \
                -exec sh -eux -c ' \
                        md5="$(md5sum "$1" | cut -d" " -f1)"; \
                        test "$md5" = "$serverMd5"; \
                ' -- '{}' ';' \
                -exec ln -svfT 'redis-server' '{}' ';' \
        ; \
        \
        rm -r /usr/src/redis; \
        \
        runDeps="$( \
                scanelf --needed --nobanner --format '%n#p' --recursive /usr/local \
                        | tr ',' '\n' \
                        | sort -u \
                        | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
        )"; \
        apk add --no-network --virtual .redis-rundeps $runDeps; \
        # 基于前面安装卸载 .build-deps 虚拟包
        apk del --no-network .build-deps; \
        \
        redis-cli --version; \
        redis-server --version

RUN mkdir /data && chown redis:redis /data
VOLUME /data
WORKDIR /data

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD ["redis-server"]

Dockerfile 中 ENTRYPOINT 所使用的 docker-entrypoint.sh 文件内容如下。

#!/bin/sh
set -e

当第一个参数使用 -f or --some-option 这样参数时
或以 .conf 结尾,
if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then
  # 将第一个参数设为 redis-server, 方便后面启动
        set -- redis-server "$@"
fi

当启动程序是 'redis-server' 且 uid = 0 时, 即 Root 启动
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
  # 1. 调整数据目录属主为 Redis
        find . \! -user redis -exec chown redis '{}' +
  # 2. 使用 exec su-exec 组合启动redis-server,  会将当前 PID 传递给 redis-server, 并切换用户为 redis
  #    注意: 使用 exec 后, 后面脚本将不会执行
        exec su-exec redis "$0" "$@"
fi

当用户使用其它用户启动, 或者非 redis-server 服务启动适时会执行如下代码

修改默认权限 为 700
um="$(umask)"
if [ "$um" = '0022' ]; then
        umask 0077
fi

启动服务
exec "$@"

我们从几个案例中可以看出,Alpine 为容器做了很多量身打造的特性。比如 apk 的不使用缓存/虚拟包特性,容器构建带来极大方便。而 apk 所包含 su-exec 包也比 sudo 更适合容器。

不仅如此,传统镜像精简版镜像的 ps/netstat/ifconfig/ip/vi 等命令会被删掉,导致后期运维困难。而仅 5M 的 Alpine 却都自带。传统镜像去安装任何一个命令可能都要比 Alpine 本身都要大,还会增添 cve 漏洞风险,而 Alpine 几乎无需考虑这个问题。

所以,如果没有用到 glibc 的服务 Alpine 将是一个非常不错的选择。而用了 glibc 测试过兼容性问题后,Alpine 也是非常不错选择。

作者:安树博 青云科技 PaaS 中间件开发工程师
从事 PaaS 中间件服务(Redis/Memcached 等)开发工作,热衷对 NoSQL 数据库领域内技术的学习与研究

Original: https://www.cnblogs.com/radondb/p/16627585.html
Author: RadonDB
Title: 容器化 | 使用 Alpine 构建 Redis 镜像

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/504938/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • kettle插入更新

    kettle实现若主键存在则更新,若主键不存在则插入 Original: https://www.cnblogs.com/cheng9999/p/14085922.htmlAuth…

    数据库 2023年6月16日
    062
  • etcd和Zookeeper孰优孰劣对比

    背景 最近在看到Pachyderm的介绍时,看到作者拿YARN和Kubernetes做类比,拿Zookeeper和etcd做对比。YARN和Kubernetes的类比还相对比较好理…

    数据库 2023年6月11日
    085
  • 获取单选按钮组的选中值

    1 var radioGroup = new Ext.form.RadioGroup({ 2 width: 130, 3 id: ‘selected’, 4 layout: ‘fo…

    数据库 2023年6月9日
    069
  • 学习笔记——Django项目中关联查询以及关联查询的筛选

    2022-10-01 关联查询: 在Django项目中使用ORM模式设置表后,进行关联查询,即两个表直接有联系的查询。 方式: 可以通过主表查询从表,也可以通过从表查询主表。 方式…

    数据库 2023年6月14日
    064
  • 常用API(Java)

    Object 场景:当我们使用toString方法想要输出对象变量时,官方提供的toString方法会直接输出对象所在的地址,而不是我们想要的对象变量,所以我们要把toString…

    数据库 2023年6月16日
    083
  • MySQL实战45讲 13

    13 | 为什么表数据删掉一半,表文件大小不变? 一个 InnoDB 表包含两部分,即: 表结构定义和 数据。 在 MySQL 8.0 版本以前, 表结构是存在以.frm 为后缀的…

    数据库 2023年6月16日
    092
  • zabbix监控配置流程

    zabbix监控配置流程 管理层次: 开发人员要加监控,需要让其提供监控指标运营人员要加监控,让其找开发要监控指标运维人员要加监控,让运营人员去找开发要监控指标。 配置层次: 1….

    数据库 2023年6月14日
    078
  • grafana+prometheus如何查看tcp连接数量

    最后解决方案 经过和负责监控的大佬了解,获得了一个可行的方案:在每个pod中新增一个sidecar容器,在容器中部署node_exporter,或者在容器中放个自动查看端口连接数并…

    数据库 2023年6月9日
    0115
  • PDF转换OFD(Java实用版)

    前言: 在项目中用到了,就写一下哈 OFD简介 百度百科:https://baike.baidu.com/item/OFD/56227163?fr=aladdin OFD(Open…

    数据库 2023年6月16日
    0119
  • JavaScript基本知识点——带你逐步解开JS的神秘面纱

    JavaScript基本知识点——带你逐步解开JS的神秘面纱 在我们前面的文章中已经深入学了HTML和CSS,在网页设计中我们已经有能力完成一个美观的网页框架 但仅仅是网页框架不足…

    数据库 2023年6月14日
    098
  • Mybatis第三方PageHelper分页插件原理

    欢迎关注公号:BiggerBoy,看更多文章 往期精品 此时commentAnalyses为Page对象(PageHelper插件包内定义的) 而Page对象继承自JDK中的Arr…

    数据库 2023年6月11日
    081
  • mysql入门到精通学习教程,浅谈MySQL是什么?

    Original: https://www.cnblogs.com/chaichaichai/p/15304638.htmlAuthor: 牛仔码农Title: mysql入门到精…

    数据库 2023年5月24日
    064
  • 高并发组件了解

    消息队列 A服务和多个服务耦合,内部维护对多个服务发送数据的接口,那么这些接口如果有的挂了,有的不需要了,那么还得修改A内部的代码,如果使用MQ,A发送消息就好,不必考虑那么多事情…

    数据库 2023年6月16日
    060
  • Mysql 执行流程

    1、逻辑剖析 sql 执行流程为:sql语句 -> 查询缓存 -> 解析器 -> 优化器 -> 执行器。 1.1 服务器处理客户端请求 ​ 客户端程序 co…

    数据库 2023年5月24日
    092
  • Golang context

    Context Go 语言中提供了 context 包,通过显示传递 context, 实现请求级别的元数据、取消信号、终止信号的传递。context 包提供了从现有的上下文值(c…

    数据库 2023年6月16日
    080
  • SQL中针对不规范数字order by排序的处理方式

    在操作数据库的时候经常需要order by进行排序,但是有的时候数据并没有很好的格式化导致排序的结果不合我们的心意,如下图: 如果我们要按照value进行排序的话,就会得到上面截图…

    数据库 2023年5月24日
    0101
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球