Docker 建站小記
阿新 • • 發佈:2021-01-24
# 一,前言
Docker 建站小記,我使用了四個映象來搭建:nginx,certbot,mysql,gradle。歡迎訪問:https://www.zzk0.top
這個網頁是從 github 上找的[個人主頁](https://github.com/dmego/home.github.io),背景用的是 bing 的桌布,中間有兩個連結,一個指向我的 github,一個指向部落格園。後端專案目前只有一個介面,當有人訪問這個網站的時候,就會向後端專案發出一個請求,由 nginx 負責將對 api.zzk0.top 的請求轉發到後端專案。
最理想的狀態是,我直接 `docker-compose up`,我就可以將專案執行起來了。不過我的方法,需要自己手動配置一下才行,一個是初始化申請 SSL 的指令碼,一個是定時分割 NGINX 日誌。之後就只需要 `docker-compose up` 就行了。
這裡簡單講講這四個映象分別是幹什麼的吧。
- nginx,這個是 http 伺服器,用來提供提供靜態資源的,這篇部落格大部分內容都是在講如何配置 nginx。
- certbot,這個用來申請 SSL 證書。設定了一個定時任務,每個月重新整理一下證書。
- mysql,後端專案資料庫。
- gradle,編譯並執行 springboot 專案。
# 二,NGINX
## 重新載入配置
修改了 NGINX 的配置,但是又不想重新啟動容器。我們可以通過向容器傳送命令來載入新配置。
```
# docker exec -it your-name nginx -s reload
2021/01/23 02:30:58 [notice] 23#23: signal process started
```
## HTTPS 配置
首先我們需要證書和金鑰,我們可以使用免費的 Let's Encrypt 來生成,不過三個月就會過期,所以需要重新整理一下。
證書的生成可以選擇在本機上還是在 Docker 裡,如果在本機上生成,我們可以選擇 standalone 模式,但是這樣的話,它需要短暫佔用一下 80 埠來驗證這個域名是否是你的。因此當你有一個正在執行的 NGINX 並且佔用了 80 埠的話,我們需要去暫停它。如果在 Docker 中生成,並且你的網站有一個根目錄,那麼可以使用 webroot 模式,它會在你的網站根目錄下生成一些檔案,然後通過域名去訪問它來驗證這個域名是不是你的,使用 webroot 模式就不需要暫停 NGINX,因此對正在執行的網站影響會小一些。
選擇前者的好處是,不需要定製 Docker,但是需要短暫停止伺服器;選擇後者的好處是,不需要停止伺服器,可移植性更好一點,本機只需要有 Docker 就能跑,但是需要定製映象比較麻煩。不過,這種比較常見的需求,我們上 docker hub 搜一搜,一找就有了:https://hub.docker.com/r/staticfloat/nginx-certbot/ 。再搜一搜,我們會發現[另一個方案](https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71):為什麼不搞一個 certbot 的容器呢?然後將 nginx 的容器和 certbot 的容器連線起來!好,那我們就加多一個容器,來幫我們生成 SSL 證書好了。
**申請**
[這篇文章](https://diamondfsd.com/lets-encrytp-hand-https/)可以參考看看 certbot 的操作。[這篇文章](https://medium.com/@pentacent/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71)提供了 certbot 和 nginx 的方案。
從文章配套的 [Github 倉庫](https://github.com/wmnnd/nginx-certbot) 中把它的指令碼和配置檔案複製貼上過來,把所有的 example.org 域名改成自己的域名,然後執行它的指令碼。如果失敗了,建議檢查一下是不是 NGINX 沒有啟動。這個指令碼不會提醒你 NGINX 沒有啟動,如果失敗了,很有可能是 NGINX 沒有配置好。
一開始跑這個指令碼,我失敗了幾次。之後逐步執行指令碼,然後啟動容器看看什麼問題。一看,才知道原來是 https 的推薦引數檔案沒有下載好,所以手動下載了那幾個檔案,然後修改指令碼為複製而不是下載。
**重定向**
nginx 將 http 重定向到 https,完整的配置檔案可以看附錄。
```
server {
listen 80;
server_name zzk0.top www.zzk0.top;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name zzk0.top www.zzk0.top;
root /var/www/html;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/zzk0.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zzk0.top/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
try_files $uri $uri/ =404;
}
}
```
## 配置子域名
假設有一個域名 test.com,我們需要將對 api.test.com 的請求轉發到 Tomcat,對 test.com 的請求則直接提供靜態網站。
這個需求比較簡單,在 server 下面設定好 server_name 就可以了。另外,我們還需要注意不要讓別人用 ip 訪問我們的網站,網上的說法是,別人可以惡意使用未備案的域名指向這個 ip,然後導致網站被封掉了。
```
server {
listen 80 default_server;
server_name _;
return 403;
}
server {
listen 443 default_server;
server_name _;
ssl_certificate /etc/letsencrypt/live/zzk0.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zzk0.top/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
return 403;
}
```
## 訪問日誌
為了記錄網站的訪問記錄,可以使用 NGINX 的訪問日誌來做記錄,按天分割。NGINX 的映象裡面沒有 crontab,所以這裡就用主機來做。方法是每天定時將檔案重新命名,然後 reload NGINX。不過這個部分比較不智慧,每次更換主機的時候,都需要手動去配置,還需要修改下面的路徑。
```
#!/bin/sh
LOGS_PATH=/root/home/data/nginx
TODAY=$(date -d 'today' +%Y-%m-%d)
mv ${LOGS_PATH}/error.log ${LOGS_PATH}/error_${TODAY}.log
mv ${LOGS_PATH}/access.log ${LOGS_PATH}/access_${TODAY}.log
docker exec -i home-nginx nginx -s reload
```
接下來,將以下內容加入到 `/etc/crontab` 中,這樣就可以執行每日任務了。每天凌晨4點的時候,自動分割日誌。
```
0 4 * * * root /root/home/nginx/daily_log.sh >> /root/home/data/nginx/daily_log.log 2>&1
```
# 三,MySQL
有時候我們需要進入資料庫的映象裡面去看看資料。
```shell
# 進入映象
docker exec -it your-name bash
# 登入
mysql -uroot -p
# 羅列資料庫
show database
```
# 四,後端專案
後端專案使用 SpringBoot 來做。需求是:啟動 Docker 後構建原始碼並執行。這個部分使用 gradle 的映象,啟動的時候,執行 `gradle bootRun` 就可以了。
當我們更新了後端專案之後,我們只需要重啟這個容器,就可以構建了。我的後端專案容器名字叫做 springboot,重啟一下就可以將介面更新了,不過重啟過程中的請求會失敗。
```
docker restart springboot
```
下面是 docker-compose.yml 中的配置。
```
web:
container_name: springboot
restart: always
image: gradle:6.7.1-jdk8
depends_on:
- db
volumes:
- ./api:/home/gradle/project
environment:
TZ : 'Asia/Shanghai'
command: bash -c "cd /home/gradle/project && gradle bootRun"
```
# 附錄
## NGINX 配置
```
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
upstream tomcat {
server web:8080;
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
limit_req_zone $binary_remote_addr zone=api_limit_req:10m rate=30r/m;
sendfile on;
keepalive_timeout 65;
gzip on;
include /etc/nginx/conf.d/*.conf;
# forbid access via ip address
server {
listen 80 default_server;
server_name _;
return 403;
}
server {
listen 443 default_server;
server_name _;
ssl_certificate /etc/letsencrypt/live/zzk0.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zzk0.top/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
return 403;
}
# configure zzk0.top, redirect to https and serve static files
server {
listen 80;
server_name zzk0.top www.zzk0.top;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name zzk0.top www.zzk0.top;
root /var/www/html;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/zzk0.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zzk0.top/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
try_files $uri $uri/ =404;
}
}
# configure api.zzk0.top, backend api
server {
listen 80;
server_name api.zzk0.top;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name api.zzk0.top;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/zzk0.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zzk0.top/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
limit_req zone=api_limit_req;
proxy_pass http://tomcat;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
```
## Docker 配置
```
version: '3'
services:
certbot:
container_name: home-certbot
restart: always
image: certbot/certbot
volumes:
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 720h & wait $${!}; done;'"
nginx:
container_name: home-nginx
restart: always
image: nginx:1.18.0
ports:
- 80:80
- 443:443
depends_on:
- web
links:
- web:web
environment:
TZ : 'Asia/Shanghai'
volumes:
- ./html:/var/www/html
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
- ./data/nginx:/var/log/nginx
command: "/bin/sh -c 'while :; do sleep 720h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
db:
container_name: home-db
restart: always
image: mysql:8.0.13
ports:
- 7706:3306
volumes:
- ./data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: backend_database
MYSQL_USER: root
MYSQL_PASSWORD: root
web:
container_name: springboot
restart: always
image: gradle:6.7.1-jdk8
depends_on:
- db
volumes:
- ./api:/home/gradle/project
environment:
TZ : 'Asia/Shanghai'
command: bash -c "cd /home/gradle/project && gradle bootR