-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
360 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,359 @@ | ||
--- | ||
category: Server | ||
tags: | ||
- Full Stack | ||
date: 2024-12-07 | ||
title: 全栈项目从上线、部署到运维 | ||
vssue-title: 全栈项目从上线、部署到运维 | ||
--- | ||
|
||
本文主要记录了全栈项目上线、部署、运维的整个流程,仅供参考 | ||
|
||
<!-- more --> | ||
|
||
## 基本信息 | ||
|
||
### 项目选型 | ||
- React | ||
- NestJS | ||
- MySQL | ||
|
||
### 服务器 | ||
- 阿里云 ECS | ||
- Debain 10.13 | ||
|
||
## 环境安装 | ||
|
||
### Git | ||
安装 git: | ||
```bash | ||
sudo apt update | ||
sudo apt install git | ||
``` | ||
|
||
生成 ssh key 后配置到 Github 中: | ||
```bash | ||
ssh-keygen -t ed25519 -C "youremail@email.com" | ||
``` | ||
|
||
### MySQL | ||
下载 MySQL 5.7,或者前往[官网](https://downloads.mysql.com/archives/community/)寻找合适的版本: | ||
```bash | ||
curl -OL https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz | ||
``` | ||
|
||
然后解压缩: | ||
```bash | ||
sudo tar -xzf mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz -C /usr/local | ||
``` | ||
|
||
重命名目录: | ||
```bash | ||
cd /user/local | ||
sudo mv mysql-5.7.44-linux-glibc2.12-x86_64 mysql | ||
``` | ||
|
||
创建 MySQL 用户和组: | ||
```bash | ||
sudo groupadd mysql | ||
sudo useradd -r -g mysql -s /bin/false mysql | ||
``` | ||
|
||
初始化数据库: | ||
```bash | ||
cd /usr/local/mysql | ||
sudo mkdir mysql-files | ||
sudo chmod 750 mysql-files | ||
sudo chown -R mysql:mysql ./ | ||
sudo bin/mysqld --initialize --user=mysql | ||
``` | ||
|
||
此时终端中会显示随机生成的密码: | ||
``` | ||
[Note] A temporary password is generated for root@localhost: %sqwtmz5p(Xe | ||
``` | ||
|
||
设置权限和目录: | ||
```bash | ||
sudo chown -R root . | ||
sudo chown -R mysql data mysql-files | ||
``` | ||
|
||
安装启动脚本: | ||
```bash | ||
sudo cp support-files/mysql.server /etc/init.d/mysql | ||
sudo chmod +x /etc/init.d/mysql | ||
sudo update-rc.d mysql defaults | ||
``` | ||
|
||
启动 MySQL 服务: | ||
```bash | ||
sudo systemctl start mysql | ||
``` | ||
|
||
查询 MySQL 服务状态: | ||
```bash | ||
sudo systemctl status mysql | ||
``` | ||
|
||
修改密码: | ||
```bash | ||
# 登录 root | ||
mysql -u root -p | ||
|
||
# 执行 SQL | ||
ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password'; | ||
``` | ||
|
||
### MySQL 远程连接 | ||
创建一个新的用户用于远程连接: | ||
```bash | ||
# % 表示任意地址,也可以指定具体的 ip | ||
CREATE USER 'remote'@'%' IDENTIFIED BY 'newpassword' | ||
``` | ||
|
||
授予权限: | ||
```bash | ||
# 所有数据库: | ||
GRANT ALL PRIVILEGES ON *.* TO 'newuser'@'%'; | ||
|
||
# 特定数据库 | ||
GRANT ALL PRIVILEGES ON exampledb.* TO 'newuser'@'%'; | ||
``` | ||
|
||
刷新权限: | ||
```bash | ||
FLUSH PRIVILEGES; | ||
``` | ||
|
||
配置 MySQL 允许远程连接,编辑或新建 `/etc/mysql/my.cnf` 或 `/etc/my.cnf`: | ||
```bash | ||
[mysqld] | ||
bind-address = 0.0.0.0 | ||
``` | ||
|
||
然后重启 MySQL 服务: | ||
```bash | ||
sudo systemctl restart mysql | ||
``` | ||
|
||
### NodeJS | ||
安装 nvm 来管理 NodeJS 版本,官方的安装方式对于网络连通性有要求,采用下面的方法: | ||
```bash | ||
git clone https://github.com/nvm-sh/nvm.git | ||
bash nvm/install.sh | ||
``` | ||
|
||
安装 NodeJS 20: | ||
```bash | ||
nvm install 20 | ||
``` | ||
|
||
设置 npm 镜像: | ||
```bash | ||
echo 'registry=https://registry.npmmirror.com/' > ~/.npmrc | ||
``` | ||
|
||
### Nginx | ||
安装 Nginx: | ||
```bash | ||
sudo apt install nginx | ||
``` | ||
|
||
## 服务部署 | ||
### NodeJS 服务 | ||
使用 `pm2` 来启动 NodeJS 服务: | ||
```bash | ||
pm2 start ecosystem.config.js --env production | ||
``` | ||
|
||
配置文件示例: | ||
```js | ||
// ecosystem.config.js | ||
module.exports = { | ||
apps: [ | ||
{ | ||
name: 'my-app', | ||
script: 'dist/main.js', | ||
instances: 'max', | ||
exec_mode: 'cluster', | ||
env: { | ||
NODE_ENV: 'development' | ||
}, | ||
env_production: { | ||
NODE_ENV: 'production' | ||
} | ||
} | ||
] | ||
}; | ||
``` | ||
|
||
### Nginx | ||
配置 Nginx 转发服务端接口: | ||
```bash | ||
cd /etc/nginx/sites-available/ | ||
vi api.myhost.com | ||
``` | ||
|
||
配置文件中添加: | ||
```nginx | ||
server { | ||
listen 80; | ||
server_name api.myhost.com; | ||
location / { | ||
proxy_pass http://localhost:3000; | ||
proxy_set_header Host $host; | ||
proxy_set_header X-Real-IP $remote_addr; | ||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
proxy_set_header X-Forwarded-Proto $scheme; | ||
} | ||
} | ||
``` | ||
新建软连接以启动对应配置: | ||
```bash | ||
sudo ln -s /etc/nginx/sites-available/api.myhost.com /etc/nginx/sites-enabled/ | ||
``` | ||
|
||
重启 Nginx 应用配置: | ||
```bash | ||
sudo systemctl restart nginx | ||
``` | ||
|
||
配置 Nginx 转发前端静态资源,流程和前面一致,仅 Nginx 配置略有不同: | ||
```nginx | ||
server { | ||
listen 80; | ||
server_name www.example.com; | ||
root /root/projects/example/dist; | ||
index index.html index.htm; | ||
location / { | ||
try_files $uri $uri/ =404; | ||
} | ||
} | ||
``` | ||
|
||
### HTTPS 证书申请 | ||
通过 certbot 申请 SSL 证书,安装对应 pkg: | ||
```bash | ||
sudo apt install certbot python3-certbot-nginx | ||
``` | ||
|
||
根据提示完成配置,将自动下载证书,完成 nginx 配置并重启: | ||
```bash | ||
sudo certbot --nginx -d api.myhost.com | ||
``` | ||
|
||
证书有效期只有 3 个月,可以通过脚本实现自动续签,参考 [certbot-dns-aliyun](https://github.com/justjavac/certbot-dns-aliyun): | ||
```bash | ||
# 安装 certbot-dns-aliyun | ||
wget https://cdn.jsdelivr.net/gh/justjavac/certbot-dns-aliyun@main/alidns.sh | ||
sudo cp alidns.sh /usr/local/bin | ||
sudo chmod +x /usr/local/bin/alidns.sh | ||
sudo ln -s /usr/local/bin/alidns.sh /usr/local/bin/alidns | ||
rm alidns.sh | ||
``` | ||
|
||
测试证书续期: | ||
```bash | ||
certbot renew --manual --preferred-challenges dns --manual-auth-hook "alidns" --manual-cleanup-hook "alidns clean" --dry-run | ||
``` | ||
|
||
正式续期时去掉 `--dry-run`: | ||
```bash | ||
certbot renew --manual --preferred-challenges dns --manual-auth-hook "alidns" --manual-cleanup-hook "alidns clean" | ||
``` | ||
|
||
设置定时任务: | ||
```bash | ||
crontab -e | ||
``` | ||
|
||
设置每天凌晨 1 点 1 分执行: | ||
```bash | ||
1 1 */1 * * certbot renew --manual --preferred-challenges dns --manual-auth-hook "alidns" --manual-cleanup-hook "alidns clean" --deploy-hook "nginx -s reload" | ||
``` | ||
|
||
查询 crontab 执行记录: | ||
```bash | ||
grep CRON /var/log/syslog | ||
``` | ||
|
||
### CD | ||
以 `Github Workflow` 为例,实现自动化部署。新建 `.github/workflows/deploy.yml`: | ||
```yaml | ||
name: Deploy to Server | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
deploy: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v2 | ||
|
||
- name: Set up SSH | ||
uses: webfactory/ssh-agent@v0.5.3 | ||
with: | ||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} | ||
|
||
- name: Run deployment script | ||
run: | | ||
ssh -o StrictHostKeyChecking=no user@your-server-ip << 'EOF' | ||
cd /path/to/your/repo | ||
git pull origin main | ||
pnpm install | ||
pnpm run build | ||
pm2 restart your-app-name | ||
EOF | ||
``` | ||
新建一个用户用于托管 CD 流程: | ||
```bash | ||
sudo adduser deploy | ||
``` | ||
|
||
给用户设置某个目录的所有权: | ||
```bash | ||
sudo chown -R deploy:deploy /var/www/html | ||
``` | ||
|
||
切换至对应用户: | ||
```bash | ||
sudo su - deploy | ||
``` | ||
|
||
|
||
## 服务运维 | ||
### 日志 | ||
使用 `pm2-logrotate` 来划分 pm2 中的 NestJS 日志 | ||
```bash | ||
# 安装 pm2-logrotate | ||
pm2 install pm2-logrotate | ||
``` | ||
|
||
使日志按每小时划分: | ||
```bash | ||
# 设置日志文件名 | ||
pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-00-00 | ||
pm2 set pm2-logrotate:rotateInterval "0 * * * *" | ||
``` | ||
|
||
使用自定义的 logger 来实现 ORM 日志按小时划分: | ||
```ts | ||
class CustomLogger implements Logger { | ||
private getLogFileName(): string { | ||
const now = dayjs().set('minute', 0).set('second', 0); | ||
return path.join(LOG_DIR, `ormlogs-${now.format('YYYY-MM-DD_HH-mm-ss')}.log`); | ||
} | ||
|
||
// ... | ||
} | ||
``` |