Cron容器部署

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

  • 准备工作

  • crontab 文件

  • Dockerfile

  • docker-compose.yaml文件

  • 执行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」 的目录

# cron容器部署

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

```bash
#说明:
#不能使用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

- 准备工作

- crontab 文件

- Dockerfile

- docker-compose.yaml文件

- 执行docker-compose.yaml

```bash
#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`
    ```bash
    "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"
            }
        ]
    ```
- 宿主机文件准备:
  ```bash
  /docker-data/cron/scripts/
  ├── acme
      └── acme-sh  #此文件夹需要拷贝过来,chown -R root:root acme-sh
      └── container-nginx-reload.sh #此文件需要拷贝过来,核对容器中的nginx容器名称

  ```
  - acme-sh 目录内容:[https://github.com/acmesh-official/acme.sh](https://github.com/acmesh-official/acme.sh) 中的内容
  - container-nginx-reload.sh
  ```bash
  #!/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
  ```bash
  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
  ```
- 安装时可能会提示:

```bash
 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 运行参数说明

```bash
#    --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.
```

- 生成证书

```bash
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

```

- 安装证书

```bash
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文件

```bash
#每天夜间 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容器

```bash
docker restart supercronic
```

- 测试
  - 在浏览器中打开网站 https://${web_site_name} 看证书的日期是否是最新的
  - 使用curl查看证书
    - 执行: `curl -vI https://www.rstone.com.cn --ssl`
    - 参数说明:
      - -v:显示详细输出(verbose)。
      - -I:只请求 HEAD 信息(避免下载完整内容)。
      - --ssl:强制使用 SSL(默认已启用)。
    - 执行结果:
    ```bash
    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
    ```
    - 重点内容
    ```bash
    * 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内部测试
  ```bash
  #执行 看是否正确「在容器内部」
  /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」 的目录