在现代前后端分离或静态站点,将网站代码安全快速的推送到 Web 服务器提高效率的必由之路。
架构
开发者将网站代码 Push 到 GitLab 的主分支。GitLab CI 启动一个临时的轻量级 Runner,通过 SSH 触发 Web Server 主动拉取 Git 代码。专用的 pushgit 容器与 Nginx 容器分离,只有静态资源/代码所在的目录是共享挂载的。CI 只与 pushgit 容器通信,没有任何 Web Server 的其它权限。
Web Server 端配置与搭建
pushgit 容器
这个容器用于接受 GitLab CI 的SSH访问,触发拉取 Git 更新网站文件。
- 生成容器中 SSHd 的主机密钥存放在 host_key 目录下,保持容器重建后的主机密钥不变
- 生成一对用户密钥存放在 id_key 目录下,以后容器就是用这个私钥到 GitLab 上拉取文件
- authorized_keys 存放的是 GitLab CI 的 SSH 公钥,CI 用这个公钥来触发更新
docker-compose.yml:
pushgit:
build: ./pushgit
image: pushgit
container_name: pushgit
restart: unless-stopped
ports:
- 2222:22
environment:
- TZ=Asia/Shanghai
volumes:
- ./yaoge1:/var/www/yaoge1:rw
- ./yaoge2:/var/www/yaoge2:rw
Dockerfile:
FROM alpine:latest
RUN sed -i 's#https\?://dl-cdn.alpinelinux.org/alpine#https://mirrors.cernet.edu.cn/alpine#g' /etc/apk/repositories
RUN apk update --no-cache \
&& apk upgrade --no-cache \
&& apk add --no-cache tzdata ca-certificates openssh-server openssh-client git \
&& update-ca-certificates \
&& rm -rf /var/cache/apk/*
COPY --chmod=0600 host_key/ssh_host_*_key /etc/ssh/
COPY --chmod=0644 host_key/ssh_host_*_key.pub /etc/ssh/
RUN echo "PasswordAuthentication no" > /etc/ssh/sshd_config.d/disable_password.conf \
&& echo "StrictHostKeyChecking accept-new" > /etc/ssh/ssh_config.d/accept_hostkey.conf
RUN adduser -D -u 1000 -h /home/pushgit -s /home/pushgit/pushgit.sh pushgit && passwd -u pushgit
COPY --chown=pushgit:pushgit --chmod=0700 pushgit.sh /home/pushgit/
RUN install -d -m 0700 -o pushgit -g pushgit /home/pushgit/.ssh
COPY --chown=pushgit:pushgit --chmod=0600 id_key/id_* /home/pushgit/.ssh/
COPY --chown=pushgit:pushgit --chmod=0600 authorized_keys /home/pushgit/.ssh/
RUN ssh-keyscan git.yaoge123.com >> /home/pushgit/.ssh/known_hosts \
&& chown pushgit:pushgit /home/pushgit/.ssh/known_hosts \
&& chmod 0644 /home/pushgit/.ssh/known_hosts
CMD ["/usr/sbin/sshd", "-De"]
pushgit.sh:
#!/bin/sh
set -e
echo "$2: "
cd "/var/www/$2" || { echo "Directory not found!"; exit 1; }
git config --global safe.directory "/var/www/$2"
git fetch --all
git reset --hard @{upstream}
git clean -fd
authorized_keys:
restrict,command="yaoge1" ssh-ed25519 AxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxZ gitlab-yaoge1
restrict,command="yaoge2" ssh-ed25519 AxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxU gitlab-yaoge2
Nginx 配置
server {
listen 80;
listen [::]:80;
server_name yaoge1.yaoge123.com;
server_tokens off;
return 301 https://$server_name$request_uri;
}
server {
listen *:443 ssl;
listen [::]:443 ssl;
server_name yaoge1.yaoge123.com;
server_tokens off;
include ssl/acme-challenge.conf;
include ssl/yaoge123_com.conf;
location ~ /\.git {
return 404;
}
root /var/www/yaoge1;
}
GitLab 端配置
创建一个用户(比如 WebServerPush),把上述 id_key/id_*.pub 加入用户的 SSH Keys 下,把这个用户加入到项目成员中,给 Reporter 角色。这个用户是公用的,所有类似的项目都添加这个用户即可。
生成一对网站项目专用的 key,其中公钥放到上述 authorized_keys 中,私钥经BASE64编码后存到CI/CD变量中。
| 变量键值 (Key) | 说明 |
| SSH_PRIVATE_KEY_BASE64 | SSH 私钥的Base64编码,选择 Masked and hidden 和 Protect variable |
| SERVER_IP | Web Server 的 IP |
| SERVER_PORT | pushgit 容器暴露的 SSH 端口(如上文示例的 2222) |
| SSH_USER | pushgit 容器内的操作账户(如上文示例的 pushgit) |
.gitlab-ci.yml
deploy_to_server:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client coreutils
- echo "$SSH_PRIVATE_KEY_BASE64" | base64 -d > ./ssh_key
- chmod 400 ./ssh_key
script:
- ssh -o StrictHostKeyChecking=accept-new -i ./ssh_key -T -p $SERVER_PORT $SSH_USER@$SERVER_IP
下面只要在 Web Server 上用 git clone 初始化一下即可,后续只要 git 推送了新的文件,就会自动触发更新。