cron容器部署

1. 方案一:使用shell语言部署

#说明:
#不能使用cron,不适合在容器运行,包括权限等。
#使用supercronic,版本v0.2.33
#supercronic 官网  https://github.com/aptible/supercronic

#第一次执行
sudo -i

#准备工作copy supercronic 到Dockerfile同级目录
#设置权限
#chown root:root ./supercronic

#第二次执行

#crontab_filename
crontab_filename="crontab"
script_hello_world_filename="hello_world.sh"

#host
#/docker-data/dockerfiles/cron 存放Dockerfile文件
host_dockerfiles_path="/docker-data/dockerfiles/cron"
host_dockerfile_filename=${host_dockerfiles_path}/Dockerfile
host_dockerfiles_path_scripts_path=${host_dockerfiles_path}/scripts
host_dockerfiles_etc_conf_d_path=${host_dockerfiles_path}/etc/cron.d
host_dockerfiles_etc_conf_d_crontab_filename=${host_dockerfiles_path}/etc/cron.d/${crontab_filename}
host_relative_dockerfiles_etc_conf_d_crontab_filename=/etc/cron.d/${crontab_filename}
#/docker-data/cron 存放容器挂载目录
host_data_cron_path="/docker-data/cron"
host_crontab_filename=${host_data_cron_path}/${crontab_filename}
host_scripts_path=${host_data_cron_path}/scripts
host_script_filename=${host_scripts_path}/${script_filename}
host_log_path=${host_data_cron_path}/log
host_script_hello_world_log_filename=${host_log_path}/hello-world.log

#映像
#映像名称
image_name="supercronic:alpine"
#crontab_path
image_cron_d_path=/etc/cron.d
#crontab_filename
image_cron_d_crontab_filename=${image_cron_d_path}/${crontab_filename}
#scripts_path
image_scripts_path="/scripts"
#log_path
image_log_path=/var/log
#hello-world.log
image_hello_world_log_filename=${image_log_path}/hello-world.log

#container
#容器名称
docker_container_name="supercronic"

#创建目录
#host
if [ ! -d ${host_dockerfiles_path} ]; then
    mkdir -p ${host_dockerfiles_path}
fi
if [ ! -d ${host_dockerfiles_path_scripts_path} ]; then
  mkdir -p ${host_dockerfiles_path_scripts_path}
fi
if [ ! -d ${host_dockerfiles_etc_conf_d_path} ]; then
  mkdir -p ${host_dockerfiles_etc_conf_d_path}
fi
if [ ! -d ${host_log_path} ]; then
    mkdir -p ${host_log_path}
fi
if [ ! -d ${host_scripts_path} ]; then
  mkdir -p ${host_scripts_path}
fi

#crontabl
echo "* * * * * echo hello-world >> ${image_hello_world_log_filename} " > \
${host_dockerfiles_etc_conf_d_crontab_filename}
chmod 644 ${host_dockerfiles_etc_conf_d_crontab_filename}
cp ${host_dockerfiles_etc_conf_d_crontab_filename} ${host_crontab_filename}

#第三次执行
#创建Dockerfile文件
cat > ${host_dockerfile_filename} << eof
# 使用 Alpine 作为基础镜像
FROM alpine:latest

# 安装必要的包,及supercronic
# curl openssl ca-certificates jq 是acmesh需要的 
#tzdata 时区相关包
RUN apk add --no-cache curl bash openssl ca-certificates jq tzdata
RUN rm -rf /var/cache/apk/*
#设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
ENV TZ=Asia/Shanghai

#RUN curl -Lo /usr/local/bin/supercronic https://github.com/aptible/supercronic/releases/latest/download/supercronic-linux-amd64
COPY ./supercronic /usr/local/bin/supercronic
RUN chmod +x /usr/local/bin/supercronic

# 创建一个目录用于存放脚本和配置文件
RUN mkdir -p ${image_cron_d_path} ${image_scripts_path}

# 将定时任务配置文件和脚本复制到容器中
COPY ./etc/cron.d/crontab ${image_cron_d_crontab_filename}
#COPY my_script.sh /scripts/my_script.sh

# 设置执行权限
#RUN chmod +x /scripts/my_script.sh
RUN chmod 0644 ${image_cron_d_crontab_filename}

#acme需要的配置
#创建网站ssl相关目录
#RUN mkdir -p /nginx/config/conf.d/ssl
#RUN mkdir -p /nginx/html/ssl
#创建docker映射目录
#RUN mkdir -p /var/run

# 启动 Supercronic 并读取 cron 配置文件
CMD ["/usr/local/bin/supercronic", "${image_cron_d_crontab_filename}"]

eof

#第四次执行
#生成映像,最后一个参数是设置命令执行的当前目录
docker build -t ${image_name} -f ${host_dockerfile_filename}  ${host_dockerfiles_path}

#第五次执行
#运行容器
#里面包含了:acme 需要的增加nginx的映射目录 和 docker的映射 
docker run -d \
  -v ${host_crontab_filename}:${image_cron_d_crontab_filename} \
  -v ${host_scripts_path}:${image_scripts_path} \
  -v ${host_log_path}:${image_log_path} \
  -v /docker-data/nginx/config/conf.d/ssl:/nginx/config/conf.d/ssl \
  -v /docker-data/nginx/html/ssl:/nginx/html/ssl \
  -v /var/run/docker.sock:/var/run/docker.sock \
  --name ${docker_container_name} \
  --restart always \
  ${image_name}
  
#测试
#查看是否每分钟会多出一个 hello-world 的内容
cat $host_script_hello_world_log_filename

#使用
#修改配置文件,可以注释掉不用的hello-world样例
vim $host_dockerfiles_etc_conf_d_crontab_filename
#重启容器
docker restart $docker_container_name
#查看日志
docker logs $docker_container_name

2.方案二「推荐」:docker-compose

  • 准备工作
tree /docker-data/docker-composes/cron/
/docker-data/docker-composes/cron/
├── Dockerfile            #见下文
├── etc
│   └── cron.d
│       └── crontab       #见下文
├── scripts
└── supercronic           #通过scp拷贝到服务器
└── docker-compose.yaml   #见下文

tree /docker-data/cron
/docker-data/cron
├── crontab             #见下文
├── log
│   └── hello-world.log #空文件即可
└── scripts
  • crontab 文件
#/docker-data/dockerfiles/cron/etc/cron.d/crontab
* * * * * echo hello-world >> /var/log/hello-world.log 
  • Dockerfile
#/docker-data/dockerfiles/cron/Dockerfile
# 使用 Alpine 作为基础镜像
FROM alpine:latest

# 安装必要的包,及supercronic
# curl openssl ca-certificates jq 是acmesh需要的 
#tzdata 时区相关包
RUN apk add --no-cache curl bash openssl ca-certificates jq tzdata
RUN rm -rf /var/cache/apk/*
#设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone
ENV TZ=Asia/Shanghai

#RUN curl -Lo /usr/local/bin/supercronic https://github.com/aptible/supercronic/releases/latest/download/supercronic-linux-amd64
#github.com不好访问,把文件下载下来,直接COPY
COPY ./supercronic /usr/local/bin/supercronic
RUN chmod +x /usr/local/bin/supercronic

# 创建一个目录用于存放脚本和配置文件
RUN mkdir -p /etc/cron.d /scripts

# 将定时任务配置文件和脚本复制到容器中
COPY ./etc/cron.d/crontab /etc/cron.d/crontab

# 设置执行权限
#RUN chmod +x /scripts/my_script.sh
RUN chmod 0644 /etc/cron.d/crontab

#acme需要的配置
#创建网站ssl相关目录
RUN mkdir -p /nginx/config/conf.d/ssl
RUN mkdir -p /nginx/html/ssl
#创建docker映射目录
RUN mkdir -p /var/run

# 启动 Supercronic 并读取 cron 配置文件
CMD ["/usr/local/bin/supercronic", "/etc/cron.d/crontab"]
  • docker-compose.yaml文件
#/docker-data/docker-compose/cron/docker-compose.yaml
services:
  supercronic:
    build: .
    image: supercronic:alpine                 # 镜像版本,此jdk版本长期稳定,没有版权问题
    container_name: supercronic            # 容器名,相当于docker run命令中的--name
    restart: unless-stopped        # 之前是什么状态,docker重启后,保持之前的状态(如果之前是stop,那docker重启时,也是stop状态)
    volumes:                                           # 数据卷挂载路径设置,将本机目录映射到容器目录,相当于docker run命令中的-v
      - /docker-data/cron/crontab:/etc/cron.d/crontab
      - /docker-data/cron/scripts:/scripts
      - /docker-data/cron/log:/var/log/
      - /docker-data/nginx/config/conf.d/ssl:/nginx/config/conf.d/ssl 
      - /docker-data/nginx/html/ssl:/nginx/html/ssl 
      - /var/run/docker.sock:/var/run/docker.sock 
  • 执行docker-compose.yaml
#pwd /docker-data/docker-composes/cron/
docker compose up -d
  • 查看docker-ps supercronic 是否没有重启
  • 查看docker logs supercronic 是否没有错误日志
  • 查看宿主机:/docker-data/cron/log 日志文件是否持续输出hello-world
  • 一切正常:注释掉/docker-data/cron/crontab:/etc/cron.d/crontab 计划任务,重启容器:docker restart supercronic

3.在cron下配置acmesh

  • acme官网: https://github.com/acmesh-official/acme.sh
  • acme.sh -h 显示帮助信息
  • 映射目录检查:
    • 检查容器是否包含映射目录: docker inspect supercronic
      "Mounts": [
              {
                  "Type": "bind",
                  "Source": "/docker-data/cron/log",
                  "Destination": "/var/log",
                  "Mode": "rw",
                  "RW": true,
                  "Propagation": "rprivate"
              },
              {
                  "Type": "bind",
                  "Source": "/docker-data/nginx/config/conf.d/ssl",
                  "Destination": "/nginx/config/conf.d/ssl",
                  "Mode": "rw",
                  "RW": true,
                  "Propagation": "rprivate"
              },
              {
                  "Type": "bind",
                  "Source": "/docker-data/nginx/html/ssl",
                  "Destination": "/nginx/html/ssl",
                  "Mode": "rw",
                  "RW": true,
                  "Propagation": "rprivate"
              },
              {
                  "Type": "bind",
                  "Source": "/var/run/docker.sock",
                  "Destination": "/var/run/docker.sock",
                  "Mode": "rw",
                  "RW": true,
                  "Propagation": "rprivate"
              },
              {
                  "Type": "bind",
                  "Source": "/docker-data/cron/crontab",
                  "Destination": "/etc/cron.d/crontab",
                  "Mode": "rw",
                  "RW": true,
                  "Propagation": "rprivate"
              },
              {
                  "Type": "bind",
                  "Source": "/docker-data/cron/scripts",
                  "Destination": "/scripts",
                  "Mode": "rw",
                  "RW": true,
                  "Propagation": "rprivate"
              }
          ]
      
  • 宿主机文件准备:
    /docker-data/cron/scripts/
    ├── acme
        └── acme-sh  #此文件夹需要拷贝过来,chown -R root:root acme-sh
        └── container-nginx-reload.sh #此文件需要拷贝过来,核对容器中的nginx容器名称
    
    
    #!/bin/bash
    
    # 定义变量
    CONTAINER_NAME="nginx"  # 替换为你的容器名称
    COMMAND='["nginx", "-s", "reload"]'
    
    # 创建 exec 实例
    EXEC_ID=$(curl -s --unix-socket /var/run/docker.sock -X POST \
      -H "Content-Type: application/json" \
      -d "{
            \"AttachStdout\": true,
            \"AttachStderr\": true,
            \"Cmd\": $COMMAND
          }" \
      http://localhost/containers/$CONTAINER_NAME/exec | jq -r '.Id')
    
    # 检查是否成功获取 EXEC_ID
    if [ -z "$EXEC_ID" ]; then
      echo "Failed to create exec instance."
      exit 1
    fi
    
    # 启动 exec 实例
    curl -s --unix-socket /var/run/docker.sock -X POST \
      -H "Content-Type: application/json" \
      -d '{}' \
      http://localhost/exec/$EXEC_ID/start
    
    
  • 安装acmesh
    docker exec -it supercronic bash #进入容器
    #以下命令在容器内部执行
    accountemai="lidongzhang@rstone.com.cn"
    web_site_name="api.rstone.net.cn"
    cd /scripts/acme/acme-sh
    ./acme.sh --install  \
    --home /scripts/acme/home \
    --accountemail  ${accountemai} \
    --log /scripts/acme/acme.sh.log \
    --nocron
    
  • 安装时可能会提示:
 It is recommended to install socat first.
 We use socat for the standalone server, which is used for standalone mode.
 If you don't want to use standalone mode, you may ignore this warning.
 #这个提示是建议安装socat,来提供standalone server,
 #我们不需要使用这种申请证书,因为我们使用的是nginx
  • acme.sh 运行参数说明
#    --home is a customized dir to install acme.sh in. By default, it installs into ~/.acme.sh
#    --config-home is a writable folder, acme.sh will write all the files(including cert/keys, configs) there. By default, it's in --home
#    --cert-home is a customized dir to save the certs you issue. By default, it's saved in --config-home.
#    --accountemail is the email used to register an account to Let's Encrypt, you will receive a renewal notice email here.
#    --accountkey is the file saving your account private key. By default, it's saved in --config-home.
#    --useragent is the user-agent header value used to send to Let's Encrypt.
#    --nocron install acme.sh without cronjob
#    --issue Issue a cert.
  • 生成证书
mkdir -p /nginx/html/ssl/${web_site_name}
cd /scripts/acme/home
#设置联系人邮箱
./acme.sh --register-account -m lidongzhang@rstone.com.cn --home /scripts/acme/home
#更改默认 CA, 否则生成证书的时候会报错
./acme.sh --set-default-ca --server letsencrypt --home /scripts/acme/home
#申请证书
./acme.sh --issue -d ${web_site_name} -w /nginx/html/ssl/${web_site_name} --home /scripts/acme/home

  • 安装证书
mkdir -p /nginx/config/conf.d/ssl/${web_site_name}
cd /scripts/acme/home
./acme.sh --install-cert -d ${web_site_name} \
--key-file       /nginx/config/conf.d/ssl/${web_site_name}/key.pem  \
--fullchain-file /nginx/config/conf.d/ssl/${web_site_name}/cert.pem \
--home /scripts/acme/home \
--reloadcmd     "/scripts/acme/container-nginx-reload.sh"
  • 给网站配置ssl,完善网站的nginx配置文件的https「ssl」部分
  • 配置crontab文件
#每天夜间 23:25 分执行, All the certs will be renewed automatically every 60 days.
echo "25 23 * * * /scripts/acme/home/acme.sh --cron --home /scripts/acme/home > /dev/null"  >> /etc/cron.d/crontab
  • 退出容器
  • 重启cron容器
docker restart supercronic
  • 测试
    • 在浏览器中打开网站 https://${web_site_name} 看证书的日期是否是最新的
    • 使用curl查看证书
      • 执行: curl -vI https://www.rstone.com.cn --ssl
      • 参数说明:
        • -v:显示详细输出(verbose)。
        • -I:只请求 HEAD 信息(避免下载完整内容)。
        • --ssl:强制使用 SSL(默认已启用)。
      • 执行结果:
      Warning: --ssl is an insecure option, consider --ssl-reqd instead
      * Host www.rstone.com.cn:443 was resolved.
      * IPv6: (none)
      * IPv4: 1.94.249.37
      *   Trying 1.94.249.37:443...
      * ALPN: curl offers h2,http/1.1
      * TLSv1.3 (OUT), TLS handshake, Client hello (1):
      *  CAfile: /etc/ssl/cert.pem
      *  CApath: /etc/ssl/certs
      * TLSv1.3 (IN), TLS handshake, Server hello (2):
      * TLSv1.2 (IN), TLS handshake, Certificate (11):
      * TLSv1.2 (IN), TLS handshake, Server key exchange (12):
      * TLSv1.2 (IN), TLS handshake, Server finished (14):
      * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
      * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
      * TLSv1.2 (OUT), TLS handshake, Finished (20):
      * TLSv1.2 (IN), TLS handshake, Finished (20):
      * SSL connection using TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 / x25519 / id-ecPublicKey
      * ALPN: server accepted http/1.1
      * Server certificate:
      *  subject: CN=www.rstone.com.cn
      *  start date: Jul 20 08:11:07 2025 GMT
      *  expire date: Oct 18 08:11:06 2025 GMT
      *  subjectAltName: host "www.rstone.com.cn" matched cert's "www.rstone.com.cn"
      *  issuer: C=US; O=Let's Encrypt; CN=E5
      *  SSL certificate verify ok.
      *   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
      *   Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
      *   Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
      * Connected to www.rstone.com.cn (1.94.249.37) port 443
      * using HTTP/1.x
      > HEAD / HTTP/1.1
      > Host: www.rstone.com.cn
      > User-Agent: curl/8.12.1
      > Accept: */*
      > 
      * Request completely sent off
      < HTTP/1.1 200 OK
      HTTP/1.1 200 OK
      < Server: nginx/1.22.0
      Server: nginx/1.22.0
      < Date: Sun, 20 Jul 2025 09:44:41 GMT
      Date: Sun, 20 Jul 2025 09:44:41 GMT
      < Content-Type: text/html;charset=utf-8
      Content-Type: text/html;charset=utf-8
      < Connection: keep-alive
      Connection: keep-alive
      < x-powered-by: Nuxt
      x-powered-by: Nuxt
      < 
      
      * Connection #0 to host www.rstone.com.cn left intact
      
      • 重点内容
      * Server certificate:
      #域名
      *  subject: CN=www.rstone.com.cn
      #证书开始时间
      *  start date: Jul 20 08:11:07 2025 GMT
      #证书到期时间
      *  expire date: Oct 18 08:11:06 2025 GMT
      *  subjectAltName: host "www.rstone.com.cn" matched cert's "www.rstone.com.cn"
      *  issuer: C=US; O=Let's Encrypt; CN=E5
      #ssl证书验证ok
      *  SSL certificate verify ok.
      
    • 在supercronic内部测试
    #执行 看是否正确「在容器内部」
    /scripts/acme/home/acme.sh --cron --home /scripts/acme/home
    #输出内容,如果是不需要更新:
    #[Wed Mar 26 13:08:09 CST 2025] ===Starting cron===
    #[Wed Mar 26 13:08:09 CST 2025] Renewing: 'tx.rstone.com.cn'
    #[Wed Mar 26 13:08:09 CST 2025] Renewing using Le_API=https://acme-v02.api.letsencrypt.org/directory
    #[Wed Mar 26 13:08:09 CST 2025] Skipping. Next renewal time is: 2025-05-24T04:51:39Z
    #[Wed Mar 26 13:08:09 CST 2025] Add '--force' to force renewal.
    #[Wed Mar 26 13:08:09 CST 2025] Skipped tx.rstone.com.cn_ecc
    #[Wed Mar 26 13:08:09 CST 2025] Renewing: 'www.rstone.com.cn'
    #[Wed Mar 26 13:08:09 CST 2025] Renewing using Le_API=https://acme.zerossl.com/v2/DV90
    #[Wed Mar 26 13:08:09 CST 2025] Skipping. Next renewal time is: 2025-05-24T04:49:20Z
    #[Wed Mar 26 13:08:09 CST 2025] Add '--force' to force renewal.
    #[Wed Mar 26 13:08:09 CST 2025] Skipped www.rstone.com.cn_ecc
    #[Wed Mar 26 13:08:09 CST 2025] Renewing: 'www.rstone.net.cn'
    #[Wed Mar 26 13:08:09 CST 2025] Renewing using Le_API=https://acme-v02.api.letsencrypt.org/directory
    #[Wed Mar 26 13:08:09 CST 2025] Skipping. Next renewal time is: 2025-05-24T04:51:57Z
    #[Wed Mar 26 13:08:09 CST 2025] Add '--force' to force renewal.
    #[Wed Mar 26 13:08:10 CST 2025] Skipped www.rstone.net.cn_ecc
    #[Wed Mar 26 13:08:10 CST 2025] ===End cron===
    #如果需要更新里面会有更新的内容信息
    #如果出现问题,可以在后面加 --debug 参数,查看问题出在了哪里。
    
    #手动重新生成证书: 此过程包括:申请证书,安装证书,执行reloadcmd
    /scripts/acme/home/acme.sh --renew -d ${web_site_name} --force --home /scripts/acme/home
    
    #原理解释:
    #acme 的home目录「安装的时候指定的」下面:
    #文件和目录列表:
    account.conf           ca                     deploy                 notify                 www.rstone.net.cn_ecc
    acme.sh                certs                  dnsapi                 tx.rstone.com.cn_ecc
    acme.sh.env            data                   http.header            www.rstone.com.cn_ecc
    #里面保存着配置信息和所有申请域名的目录,一个域名一个目录,里面有证书和要部署到的路径
    #在我们执行证书申请和安装证书命令的时候必须指定 --home 路径,这样才可以生成域名的目录保存相关证书和配置信息
    
    • 在supercronic内部
      • /scripts/acme/home 里面每个配置过的域名会有一个 「域名_ecc」 的目录