基于K8Sv1.30.0 部署开源项目:huasenjio-compose
1、部署前本地需要支持的环境
### k8s环境 本次部署采用k8sv1.30.0 高可用,集群需要支持拉取dockerhub镜像
root@k8s-master01:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master01 Ready control-plane 3d23h v1.30.0 ## 不允许调度pod
k8s-master02 Ready control-plane 3d23h v1.30.0 ## 不允许调度pod
k8s-master03 Ready control-plane 3d23h v1.30.0 ## 不允许调度pod
k8s-node01 Ready worker 3d23h v1.30.0
k8s-node02 Ready worker 3d23h v1.30.0
### k8s存储环境 至少需要有一个默认存储,nfs ceph都行,本次已rook部署的ceph为例,root部署ceph存储参考链接:https://www.xxjstl.cn/archives/3571e068-1faa-46d0-9a86-37bfee685508
root@k8s-master01:~# kubectl get storageclasses.storage.k8s.io
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 2d7h
rook-ceph-block (default) rook-ceph.rbd.csi.ceph.com Delete Immediate true 3d1h
rook-cephfs rook-ceph.cephfs.csi.ceph.com Delete Immediate true 3d1h
root@k8s-master01:~#
### k8s环境集群网络暴露方式,可选LoadBalancer,本次k8s工作负载暴露的方式为:LoadBalancer LoadBalancer部署参考https://www.xxjstl.cn/archives/jiu-kuberneteshe-xin-gai-nian-zhi-service#5loadbalancer%E7%B1%BB%E5%9E%8B
其他解释
-
集群nginx采用LoadBalancer暴露方式,这种方式会给工作负载应用暴露一个和集群同段的IP地址,这个ip地址是LoadBalancer地址池中可用的。
-
在k8s中server 连接mongo和redis采用域名方式,同理nginx代理server也是如此,具体是如下
mongo.huasenjio.svc.cluster.local ## mongo的地址 redis.huasenjio.svc.cluster.local ## redis的地址 ## 以下是nginx configmap配置文件片段,里面写明了如何连接server,详细参考2.4 nginx中的configmap文件 upstream ajax_service { server server.huasenjio.svc.cluster.local:3000; ## 定义了连接名 } upstream webs_service { server server.huasenjio.svc.cluster.local:8181; ## 定义了连接名 }
-
nginx和server容器需要重新打包,打包后在工作负载节点(node节点)导入
ctr -n=k8s.io images import service.tar ctr -n=k8s.io images import huasen-nginx.tar
2、准备各项yaml文件
2.1、部署huasenjio-namespace
## 创建huasenjio所需的命名空间资源等
apiVersion: v1
kind: Namespace
metadata:
name: huasenjio
执行
root@k8s-master01:~/note/huasenjio# kubectl apply -f 01-huasenjio-namespace.yaml
namespace/huasenjio created
验证
root@k8s-master01:~/note/huasenjio# kubectl get ns | grep huasenjio
huasenjio Active 38s
2.2、部署huasenjio-mongo
# ConfigMap 定义(保持不变)
apiVersion: v1
kind: ConfigMap
metadata:
name: init-mongo-config
namespace: huasenjio
data:
init-mongo-open.js: |
/*
* @Description: 初始化数据库
*/
// 创建应用数据库用户
const huasenDB = db.getSiblingDB("huasen");
if (!huasenDB.getUser("huasenjio")) {
print("Creating huasenjio user...");
huasenDB.createUser({
user: "huasenjio",
pwd: "Mongo12345*",
roles: [{ role: "readWrite", db: "huasen" }],
});
}
---
# PVC 定义(保持不变)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongo-pvc
namespace: huasenjio
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "rook-ceph-block"
resources:
requests:
storage: 10Gi
---
# Deployment 定义(替换原来的 Pod)
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo
namespace: huasenjio
labels:
app: mongo
spec:
replicas: 1 # 单节点部署,如需高可用可改为3
selector:
matchLabels:
app: mongo
strategy:
type: Recreate # 有状态服务使用Recreate策略
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:4.2.2
ports:
- containerPort: 27017
env:
- name: TZ
value: Asia/Shanghai
- name: MONGO_INITDB_ROOT_USERNAME
value: "root"
- name: MONGO_INITDB_ROOT_PASSWORD
value: "Mongo12345*"
volumeMounts:
- name: mongo-db
mountPath: /data/db
- name: mongo-config
mountPath: /docker-entrypoint-initdb.d/
readinessProbe:
exec:
command:
- mongo
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
exec:
command:
- mongo
- --eval
- "db.adminCommand('ping')"
initialDelaySeconds: 30
periodSeconds: 10
volumes:
- name: mongo-db
persistentVolumeClaim:
claimName: mongo-pvc
- name: mongo-config
configMap:
name: init-mongo-config
items:
- key: init-mongo-open.js
path: init-mongo-open.js
---
# Service 定义(保持不变)
apiVersion: v1
kind: Service
metadata:
name: mongo
namespace: huasenjio
labels:
app: mongo
spec:
ports:
- port: 27017
name: mongo
clusterIP: None
selector:
app: mongo
type: ClusterIP
执行
root@k8s-master01:~/note/huasenjio# kubectl apply -f 02-huasenjio-mongo.yaml
configmap/init-mongo-config created
persistentvolumeclaim/mongo-pvc created
deployment.apps/mongo created
service/mongo created
验证
root@k8s-master01:~/note/huasenjio# kubectl get pod,svc -n huasenjio
NAME READY STATUS RESTARTS AGE
pod/mongo-66bb8654d5-446b2 1/1 Running 0 28s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/mongo ClusterIP None <none> 27017/TCP 28s
root@k8s-master01:~/note/huasenjio# kubectl logs -n huasenjio mongo-66bb8654d5-446b2 | tail -6
### 以下是日志片段
2025-04-11T17:44:32.451+0800 I NETWORK [listener] connection accepted from 127.0.0.1:42206 #7 (1 connection now open)
2025-04-11T17:44:32.452+0800 I NETWORK [conn7] received client metadata from 127.0.0.1:42206 conn7: { application: { name: "MongoDB Shell" }, driver: { name: "MongoDB Internal Client", version: "4.2.2" }, os: { type: "Linux", name: "Ubuntu", architecture: "x86_64", version: "18.04" } }
2025-04-11T17:44:32.454+0800 I NETWORK [listener] connection accepted from 127.0.0.1:42214 #8 (2 connections now open)
2025-04-11T17:44:32.454+0800 I NETWORK [conn8] received client metadata from 127.0.0.1:42214 conn8: { application: { name: "MongoDB Shell" }, driver: { name: "MongoDB Internal Client", version: "4.2.2" }, os: { type: "Linux", name: "Ubuntu", architecture: "x86_64", version: "18.04" } }
2025-04-11T17:44:32.455+0800 I NETWORK [conn7] end connection 127.0.0.1:42206 (1 connection now open)
2025-04-11T17:44:32.458+0800 I NETWORK [conn8] end connection 127.0.0.1:42214 (0 connections now open)
2.3、部署huasenjio-redis
# redis-all-in-one.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
namespace: huasenjio
data:
redis.conf: |
requirepass Redis12345*
bind 0.0.0.0
appendonly yes
maxmemory 1024MB
maxmemory-policy allkeys-lru
# 性能优化参数
tcp-keepalive 300
timeout 0
tcp-backlog 511
# 安全建议
protected-mode yes
rename-command FLUSHDB ""
rename-command FLUSHALL ""
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-pvc
namespace: huasenjio
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: rook-ceph-block # 使用集群默认的Rook Ceph Block存储
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: huasenjio
labels:
app: redis
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
securityContext:
runAsUser: 1000
fsGroup: 1000
containers:
- name: redis
image: redis:6.2-alpine
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
ports:
- containerPort: 6379
name: redis
env:
- name: TZ
value: Asia/Shanghai
resources:
limits:
memory: "1536Mi"
cpu: "1"
requests:
memory: "1Gi"
cpu: "500m"
volumeMounts:
- name: redis-config
mountPath: /usr/local/etc/redis/redis.conf
subPath: redis.conf
- name: redis-data
mountPath: /data
livenessProbe:
exec:
command:
- redis-cli
- -a
- Redis12345*
- PING
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command:
- redis-cli
- -a
- Redis12345*
- PING
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: redis-config
configMap:
name: redis-config
- name: redis-data
persistentVolumeClaim:
claimName: redis-pvc
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: huasenjio
labels:
app: redis
spec:
selector:
app: redis
ports:
- name: redis
protocol: TCP
port: 6379
targetPort: 6379
type: ClusterIP
执行
root@k8s-master01:~/note/huasenjio# kubectl apply -f 03-huasen-redis.yaml
configmap/redis-config created
persistentvolumeclaim/redis-pvc created
deployment.apps/redis created
service/redis created
验证
root@k8s-master01:~/note/huasenjio# kubectl get pod -n huasenjio
NAME READY STATUS RESTARTS AGE
mongo-66bb8654d5-446b2 1/1 Running 0 3m45s
redis-597d744f8-5rbh6 1/1 Running 0 51s
root@k8s-master01:~/note/huasenjio# kubectl logs -n huasenjio redis-597d744f8-5rbh6
1:C 11 Apr 2025 17:46:41.893 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 11 Apr 2025 17:46:41.893 # Redis version=6.2.17, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 11 Apr 2025 17:46:41.893 # Configuration loaded
1:M 11 Apr 2025 17:46:41.894 * monotonic clock: POSIX clock_gettime
1:M 11 Apr 2025 17:46:41.895 * Running mode=standalone, port=6379.
1:M 11 Apr 2025 17:46:41.895 # Server initialized
1:M 11 Apr 2025 17:46:41.896 * Ready to accept connections
2.4、部署huasenjio-nginx
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
namespace: huasenjio
data:
nginx.conf: | # 修改为子配置文件名称
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
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;
sendfile on;
keepalive_timeout 65;
# 请求体最大限制10m
client_max_body_size 10m;
# 哈希桶大小参数与处理器的缓存行大小对齐
server_names_hash_bucket_size 64;
# docker容器别名访问
upstream ajax_service {
server server.huasenjio.svc.cluster.local:3000;
}
upstream webs_service {
server server.huasenjio.svc.cluster.local:8181;
}
# http重定向到https
# server {
# listen 80;
# server_name localhost;
# return 301 https://$host$request_uri;
# }
# 设置缓存目录
proxy_cache_path /usr/local/nginx-cache levels=1:2 keys_zone=nginx-cache:20m max_size=50g inactive=72h;
# IP、单域名访问配置,即:分别通过"域名/portal/"、"域名/admin/"访问花森主页及后台管理界面
server {
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 8;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
# listen 443 ssl;
listen 80; # 支持IPV4访问80端口
# listen [::]:80; # 支持IPV6访问80端口
server_name localhost;
# ohttps申请的证书文件fullchain.cer(PEM格式)
# ssl_certificate /etc/nginx/certificates/cert-***.cer;
# ohttps申请的私钥文件cert.key(PEM格式)
# ssl_certificate_key /etc/nginx/certificates/cert-***/cert.key;
location /api/ {
proxy_pass http://ajax_service/;
# 配置显示客户端真实ip
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /ws/ {
proxy_pass http://webs_service/;
proxy_set_header Connection "upgrade";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /portal/ {
add_header 'Cache-Control' "no-cache";
proxy_pass http://ajax_service/public/webapp/portal/;
}
location /admin/ {
add_header 'Cache-Control' "no-cache";
proxy_pass http://ajax_service/public/webapp/admin/;
}
location ~ ^/(portal|admin|portal\/css|admin\/css)/huasen-store/ {
add_header 'Cache-Control' "no-cache";
rewrite ^/(portal|admin|portal\/css|admin\/css)/huasen-store/(.*) /huasen-store/$2 break;
proxy_pass http://ajax_service;
}
location / {
add_header 'Cache-Control' "no-cache";
proxy_pass http://ajax_service/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
# 单独配置个人页访问,即:支持huasenjio.top直接访问
server {
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 8;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
# listen 443 ssl;
listen 80;
server_name huasenjio.top;
# ohttps申请的证书文件fullchain.cer(PEM格式)
# ssl_certificate /etc/nginx/certificates/cert-***.cer;
# ohttps申请的私钥文件cert.key(PEM格式)
# ssl_certificate_key /etc/nginx/certificates/cert-***/cert.key;
location / {
add_header 'Cache-Control' "no-cache";
proxy_pass http://ajax_service/huasen-store/webapp/guide/;
}
}
# 单独配置花森主页访问,即:支持n.huasenjio.top、www.huasenjio.top直接访问
server {
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 8;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
# listen 443 ssl;
listen 80;
server_name n.huasenjio.top www.huasenjio.top;
# ohttps申请的证书文件fullchain.cer(PEM格式)
# ssl_certificate /etc/nginx/certificates/cert-***.cer;
# ohttps申请的私钥文件cert.key(PEM格式)
# ssl_certificate_key /etc/nginx/certificates/cert-***/cert.key;
location /api/ {
proxy_pass http://ajax_service/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /ws/ {
proxy_pass http://webs_service/;
proxy_set_header Connection "upgrade";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ~ ^/(css\/huasen-store|huasen-store)/ {
add_header 'Cache-Control' "no-cache";
rewrite ^/(css\/huasen-store|huasen-store)/(.*) /huasen-store/$2 break;
proxy_pass http://ajax_service;
}
location / {
add_header 'Cache-Control' "no-cache";
proxy_pass http://ajax_service/public/webapp/portal/;
}
}
# 单独配置后台管理界面访问,即:支持a.huasenjio.top直接访问
server {
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 8;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
# listen 443 ssl;
listen 80;
server_name a.huasenjio.top;
# ohttps申请的证书文件fullchain.cer(PEM格式)
# ssl_certificate /etc/nginx/certificates/cert-***.cer;
# ohttps申请的私钥文件cert.key(PEM格式)
# ssl_certificate_key /etc/nginx/certificates/cert-***/cert.key;
location /api/ {
proxy_pass http://ajax_service/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /ws/ {
proxy_pass http://webs_service/;
proxy_set_header Connection "upgrade";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location ~ ^/(css\/huasen-store|huasen-store)/ {
add_header 'Cache-Control' "no-cache";
rewrite ^/(css\/huasen-store|huasen-store)/(.*) /huasen-store/$2 break;
proxy_pass http://ajax_service;
}
location / {
add_header 'Cache-Control' "no-cache";
proxy_pass http://ajax_service/public/webapp/admin/;
}
}
# 引入默认配置
include /etc/nginx/conf.d/*.conf;
}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
namespace: huasenjio
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: rook-ceph-block
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-logs-pvc
namespace: huasenjio
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: rook-ceph-block
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: huasenjio
labels:
app: nginx
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
# 安全上下文(根据镜像实际情况调整)
securityContext:
fsGroup: 1000
containers:
- name: nginx
image: docker.io/library/huasen-nginx:1.23.1
imagePullPolicy: Never
ports:
- containerPort: 80
name: http
# 配置文件挂载(作为子配置)
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
- name: nginx-storage
mountPath: /home/www
- name: nginx-logs
mountPath: /var/log/nginx
# 健康检查(使用有效路径)
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
# 资源限制(建议添加)
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumes:
- name: nginx-config
configMap:
name: nginx-config
items:
- key: nginx.conf
path: nginx.conf
- name: nginx-storage
persistentVolumeClaim:
claimName: nginx-pvc
- name: nginx-logs
persistentVolumeClaim:
claimName: nginx-logs-pvc
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: huasenjio
labels:
app: nginx
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
执行
root@k8s-master01:~/note/huasenjio# kubectl apply -f 04-huasen-nginx.yaml
configmap/nginx-config created
persistentvolumeclaim/nginx-pvc created
persistentvolumeclaim/nginx-logs-pvc created
deployment.apps/nginx created
service/nginx created
验证
root@k8s-master01:~/note/huasenjio# kubectl get pod -n huasenjio
NAME READY STATUS RESTARTS AGE
mongo-66bb8654d5-446b2 1/1 Running 0 7m4s
nginx-69fb49758c-bwqgh 0/1 CrashLoopBackOff 3 (23s ago) 79s ## 不影响,因为没有部署server
redis-597d744f8-5rbh6 1/1 Running 0 4m10s
root@k8s-master01:~/note/huasenjio# kubectl logs -n huasenjio nginx-69fb49758c-bwqgh
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
nginx: [emerg] host not found in upstream "server.huasenjio.svc.cluster.local:3000" in /etc/nginx/nginx.conf:25 ## 报错原因无法解析,因为没有部署server,不影响
2.5、部署huasenjio-server
---
apiVersion: v1
kind: ConfigMap
metadata:
name: server-setting
namespace: huasenjio
data:
setting.json: | # 修改setting.json
{
"site": {
"brandName": "花森",
"brandUrl": "https://n.huasenjio.top/huasen-store/icon/favicon.svg",
"brandDescription": "花森起始页由可自定义网址导航、文章博客、后台管理模块组成的开源项目,专注于收录互联网优秀站点,涵盖了生活、娱乐、学习、影视、考研、工作、科技、工具等领域,提供一个信息聚合的空间,让用户高效上网冲浪的综合性平台!",
"brandKeywords": "huasenjio.top,花森起始页,花森主页,花森导航,花森博客,花生起始页,花生主页,花生网址导航,花生博客,综合门户,网址导航,实用工具",
"redirectUrl": "https://huasenjio.top/",
"origin": "",
"headHtml": "",
"bodyHtml": "",
"footerHtml": "",
"openLabelClassification": false,
"autoIconPatch": true,
"jwt": "",
"jwtLiveTime": 604800,
"cityCode": 101210101,
"notifyArticleId": null
},
"theme": {
"pure": [
{
"color": "#ffffff",
"background": "#000000"
},
{
"color": "#ffffff",
"background": "#000000"
},
{
"color": "#ffffff",
"background": "#5522FF"
}
],
"wallpaper": [
{
"headerFontColor": "#FFFFFF",
"background": "https://s2.loli.net/2023/03/31/oSz3nJB84dC5ueh.jpg"
},
{
"headerFontColor": "#000000",
"background": "https://s2.loli.net/2023/03/31/W9n7RoFvhtlpg6U.jpg"
},
{
"headerFontColor": "#FFFFFF",
"background": "huasen-store/img/1698771433825.jpg"
},
{
"headerFontColor": "#FFFFFF",
"background": "huasen-store/img/1699075427945.jpg"
},
{
"headerFontColor": "#FFFFFF",
"background": "huasen-store/img/1700315475749.jpg"
}
],
"default": {
"order": 0,
"bg": "https://s2.loli.net/2023/03/31/oSz3nJB84dC5ueh.jpg",
"color": "#FFFFFF"
}
},
"mail": {
"host": "smtp.163.com",
"port": 465,
"user": "huasenjio@163.com",
"mtp": ""
},
"nav": [
{
"label": "花森小窝",
"type": "link",
"typeConfig": {
"url": "https://huasenjio.top/",
"target": "_blank"
},
"icon": "iconfont icon-md-home"
},
{
"label": "更新日志",
"type": "article",
"typeConfig": {
"articleId": ""
},
"icon": "iconfont icon-md-stats"
},
{
"label": "关于我们",
"type": "article",
"typeConfig": {
"articleId": ""
},
"icon": "iconfont icon-md-at"
}
],
"aside": [
{
"label": "微信二维码",
"type": "html",
"typeConfig": {
"domStr": "<img src=\"https://a.huasenjio.top/huasen-store/default/1668990565457.png\" style=\"width: 64px; height: 64px\"/>"
},
"icon": "iconfont icon-weixin"
},
{
"label": "帮助文档",
"type": "article",
"typeConfig": {
"articleId": "67828ab45ecb62738adbf096"
},
"icon": "iconfont icon-md-help-circle"
}
],
"search": [
{
"url": "https://www.baidu.com/s",
"key": "word",
"params": {},
"name": "百度",
"icon": "iconfont icon-baidu"
},
{
"url": "https://www.google.com/search",
"key": "q",
"params": {},
"name": "谷歌",
"icon": "iconfont icon-chrome"
},
{
"url": "https://cn.bing.com/search",
"key": "q",
"params": {},
"name": "必应",
"icon": "iconfont icon-bing"
},
{
"url": "localhost",
"key": "instation",
"params": {},
"name": "站内",
"icon": "iconfont icon-md-planet"
},
{
"url": "https://metaso.cn/",
"key": "q",
"params": {},
"name": "秘塔",
"icon": "iconfont icon-mita"
},
{
"url": "https://dict.youdao.com/search",
"key": "q",
"params": {},
"name": "翻译",
"icon": "iconfont icon-translate"
},
{
"url": "https://xueshu.baidu.com/s",
"key": "wd",
"params": {},
"name": "学术",
"icon": "iconfont icon-md-school"
},
{
"url": "https://ya.ru/images/search",
"key": "text",
"params": {},
"name": "图搜",
"icon": "huasen-store/default/yaru-20241229235108.png"
}
]
}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: server-config
namespace: huasenjio
data:
config.js: | # 修改config.js
/*
* @Autor: huasenjio
* @Date: 2021-10-04 11:39:03
* @LastEditors: huasenjio
* @LastEditTime: 2023-02-04 14:29:05
* @Description: 后端服务配置文件
*/
const path = require('path');
const _ = require('lodash');
// 解析动态配置
let setting = {};
try {
setting = require('./setting.json');
} catch (err) {
console.error('配置解析失败', err);
}
// 获取命令行传递的参数集合
let args = process.argv.find(row => {
return /^MODE=(dev|pro)$/.test(row);
});
// 运行环境常量(dev | pro)
const MODE = args ? args.split('=')[1] : 'dev';
console.log('[Huasen Log]:运行模式 MODE =', MODE);
// 服务启动端口
const PORT_SERVER = 3000;
// 黑名单资源池
const POOL_BLACKLIST = 'POOL_BLACKLIST';
// 用户信息池
const POOL_ACCESS = 'POOL_ACCESS';
// 邮箱验证码池
const POOL_MAIL = 'POOL_MAIL';
// 令牌池
const POOL_TOKEN = 'POOL_TOKEN';
// 数据库连接配置
const DB = {
name: 'huasenjio', // MongoDB 角色名
password: 'Mongo12345*', // MongoDB 角色密码
ip: MODE === 'dev' ? '127.0.0.1' : 'mongo.huasenjio.svc.cluster.local', // 动态选择地址
port: 27017, // MongoDB 端口
dbName: 'huasen', // 数据库名
// 生成完整的 MongoDB 连接字符串(包含认证和参数)
get uri() {
return `mongodb://${this.name}:${encodeURIComponent(this.password)}@${this.ip}:${this.port}/${this.dbName}?authSource=admin`;
}
};
// Redis 连接配置
const REDIS = {
port: 6379, // Redis 端口
host: MODE === 'dev' ? '127.0.0.1' : 'redis.huasenjio.svc.cluster.local', // 动态选择地址
password: 'Redis12345*', // Redis 密码
// 生成完整的 Redis 连接字符串(可选)
get uri() {
return `redis://:${encodeURIComponent(this.password)}@${this.host}:${this.port}`;
}
};
// websocket配置
const WS = {
port: 8181,
interval: 15000,
};
// QQ邮箱服务配置示例
const QQ_MAIL = {
host: 'smtp.qq.com', // QQ邮箱厂商
port: 465,
secure: true,
auth: {
user: 'QQ邮箱', // QQ邮箱地址
pass: 'QQ邮箱mtp', // QQ邮箱地址的mtp通行码
},
};
// 网易邮箱服务配置示例
const WY_MAIL = {
host: 'smtp.163.com',
port: 465,
secure: true,
auth: {
user: '163邮箱',
pass: '163邮箱mtp',
},
};
// 必须配置项目的邮箱服务,否则无法发送验证码
const MAIL = {
host: _.get(setting, 'mail.host') || QQ_MAIL.host,
port: _.get(setting, 'mail.port') || QQ_MAIL.port,
secure: true,
auth: {
user: _.get(setting, 'mail.user') || QQ_MAIL.auth.user,
pass: _.get(setting, 'mail.mtp') || QQ_MAIL.auth.pass,
},
};
// 重定向站点,当用户访问不正确的链接时,将会重定向到以下配置的站点
const SITE = {
redirectURL: _.get(setting, 'site.redirectUrl') || 'https://huasenjio.top/',
};
/**
* 令牌密钥配置
* 密钥格式:32位数子/字母
*/
const JWT = {
screat: _.get(setting, 'site.jwt') || 'abcdefghyjklmnobqrstuvwhyz123456', // jwt加密密钥
expiresIn: _.get(setting, 'site.jwtLiveTime') || 604800, // 7天失效时间
};
// 会话
const SESSION = {
secret: '4008208820',
cookie: { maxAge: 60 * 60 * 1000 * 24 * 7 },
resave: true,
saveUninitialized: false,
};
// 文件上传配置
const STORE = {
// 文件默认允许的类型,仅支持MIME
acceptTypes: {
".jpg": "image/jpg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/vnd.microsoft.icon",
".html": "text/html",
".css": "text/css",
".js": "text/javascript",
".mp3": "audio/mpeg",
".aac": "audio/aac",
".mp4": "video/mp4",
".mpeg": "video/mpeg",
".webm": "audio/webm",
".webp": "image/webp",
".json": "application/json",
".pdf": "application/pdf",
".rar": "application/vnd.rar",
".tar": "application/x-tar",
".zip": "application/zip",
".7z": "application/x-7z-compressed",
".bz": "application/x-bzip",
".sh": "application/x-sh",
".bin": "application/octet-stream",
".csv": "text/csv",
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".xls": "application/vnd.ms-excel",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".ppt": "application/vnd.ms-powerpoint",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
},
fileSize: 1024 * 1024 * 10, // 限制10m大小
target: 'file', // 获取前端上传文件的key值
encoding: 'utf-8', // 传输的编码格式
maxFieldsSize: 1024 * 1024 * 2, // 传输类型为text的字段的大小默认不超过2m
maxFields: 10, // 默认支持1000个字段的解析
maxFilesSize: 1024 * 1024 * 10, // 传输类型为file文件的大小不超过5m
autoFiles: true, //控制是否可以上传文件
uploadPath: path.resolve(__dirname, '../huasen-store/default'), // 默认上传文件路径
};
const TASK = {
// 任务执行的时间间隔
interval: 10000,
};
// 返回状态码
const STATUS = {
SUCCESS: 200, // 正常返回
ERROR: 400, // 服务器内部错误
FORBIDDEN: 403, // 权限不足
AUTH: 401, // 无法认证,重新登录
};
/**
* 对称密钥
* 密钥格式:16位数子/字母
* 特别注意:需要部署之前修改,否则数据无法解析
*/
const SECRET_AES = ['dj38Ca8F8hag23nD', 'k4h9HdcXmEr83nsF'];
/**
* 非对称密钥要求:
* 密钥位数:2048位(bit)
* 密钥格式:PKCS#8
* 输出格式:PEM/Base64
* 推荐在线生成站点:https://try8.cn/tool/cipher/rsa
*/
// 非对称公钥
const SECRET_RSA_PUBLIC = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAykOjPXYErze4pX/r6au/
cHOpnWlyP5AO1qXblDbQ7ZbTO2QG4MLVqz938mCstV+urDfySTHBVbeA0iwg7iye
WULR7+IHHdE7QQRCNpV3t+EPq4xbKvGv9m7U6E6vh+z4SRJDuX0rWzxhbdYsIQZd
VDs8eqfbLVMjS1+BEB/S8tFsgjWpIMfMQYF1Ale3GQjy1kLturpeAVQCnAA5dXpM
KU4I4sFpDPL58DBHziPc0UkyJCt5Y9xhEvqSaTInqzFoHKnjx1xw6bM7brsZveRT
+x8PIqepeHpI2cxjRcLKl3v/hO1qOk2a4OrY+K3z36o7KS4DZQrN/7YBWx2p3ThL
pQIDAQAB
-----END PUBLIC KEY-----
`;
// 非对称私钥
const SECRET_RSA_PRIVATE = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDKQ6M9dgSvN7il
f+vpq79wc6mdaXI/kA7WpduUNtDtltM7ZAbgwtWrP3fyYKy1X66sN/JJMcFVt4DS
LCDuLJ5ZQtHv4gcd0TtBBEI2lXe34Q+rjFsq8a/2btToTq+H7PhJEkO5fStbPGFt
1iwhBl1UOzx6p9stUyNLX4EQH9Ly0WyCNakgx8xBgXUCV7cZCPLWQu26ul4BVAKc
ADl1ekwpTgjiwWkM8vnwMEfOI9zRSTIkK3lj3GES+pJpMierMWgcqePHXHDpsztu
uxm95FP7Hw8ip6l4ekjZzGNFwsqXe/+E7Wo6TZrg6tj4rfPfqjspLgNlCs3/tgFb
HandOEulAgMBAAECggEAVkkgfHm6ad1FgiTeSWMhWiGdfC+ds4wLKHq8/6+a1aCA
IFf9ryiu6k07KEUhqIZXB9UeISd+qMiNxhtZOQID02R0Fve/vXKi6ouci5ib5++1
NaO8yMcuH90MKsZWj5ACI3oNNjY1pshNcAPr83K5odNba5/sGpva9K6banuJDFiU
pwWn1+MsqiZ74tLRlPKteyvGw0b6OWjxMeIr7/G1O30wd6MiAKObKK/ODO9CQlC2
wUAIbFqewudv26ZzkMDjpVsI42p0/xRGBQDt4vGGfKiBj05fofPl3kn7kLmY+edn
/yfvONhdHjnQVzITa9DSU1IXY8lVFHEGxuWM+V3OyQKBgQDu3YFTlsiHNTcTgs/f
LgLaMtVnjFtqu01j6F6HL9mO5ytFCE9GtaEnbNVy1z5BoAm3JgVcd63sArKFiuUn
GuaVglCKbjhgFgwnGPI11QAvPAOB2Qu19XrTwVQa4C0lJ6JrGuZe+1gLbbF+7bLV
+LMUOEU9o6YFWcMQ7fRrcLguAwKBgQDYxf624LgU0ThS301Y78O5r5g2ePZ/F1W6
M1XTlSWgllYBeDezOAH12tisYCrbEpniATfhN9Lc4qlXWz+yGVNfC2PbyfTCTdhL
kwPsf/GXM8WWe4Nid+LV5bmy5loq8dCpgXzB8w4LoFuE2ZFxr57q+32Pg04taNGP
txajinQjNwKBgAwqiA3D3k7UrQt3XDMX2tlWQXxWr8lN5PEzwqzMCR64M4H+nFsT
oTOq3WxN/kPFbPlBHIDLL7aXpJQcsPM+8YOn8YY7eu+Z7+CF6sBHKw0810jjzy7j
Y/ApJql/xYzg6ereoeEwmBls6t92J+eyFRzwiMZM8YXQPpk8JXjbcuYVAoGAZrWJ
doULM3HeSgXb1CPmjPiSGl0+DgG0cMEaDWJBrdENdyzK13PWGfNTbnkyVRJ/LwJ8
w417r4UFz4pAp9YwFnyDGAScn+PadBR4a3pDseyp1h83pVRAejCayBU069wfjfD4
d7z+DqwwMMYVj9QybAw09ea1B/b+NCX/6AUV+gkCgYEAqlrfNpXSaFbxCtDy4oA3
kHRrmwkjlGj07cSBCCInw5yL1qnSPaLVaK+l4fN68+Ac0ZvH9XFYiXYLWV8vGLUm
DyeS4WrWYM2tCoqSp/JnwReRiJPYx3lSfaB1qERyTRxKyFoMgcdybyTvIDTXiOqr
sHAMEhW3k0BqdsBQ9KSbExU=
-----END PRIVATE KEY-----
`;
module.exports = {
MODE,
DB,
REDIS,
WS,
JWT,
TASK,
SESSION,
STORE,
STATUS,
MAIL,
QQ_MAIL,
WY_MAIL,
SITE,
PORT_SERVER,
POOL_BLACKLIST,
POOL_ACCESS,
POOL_MAIL,
POOL_TOKEN,
SECRET_AES,
SECRET_RSA_PUBLIC,
SECRET_RSA_PRIVATE,
};
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: huasen-store
namespace: huasenjio
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: rook-ceph-block
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: huasen-log
namespace: huasenjio
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: rook-ceph-block
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: server
namespace: huasenjio
labels:
app: server
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: server
template:
metadata:
labels:
app: server
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
initContainers:
- name: init-setting
image: busybox
command: ["sh", "-c", "cp /configmap-source/setting.json /app/huasen-server/setting.json"]
volumeMounts:
- name: server-setting # 可写 emptyDir
mountPath: /app/huasen-server
- name: configmap-source # 临时的 ConfigMap 挂载(只读)
mountPath: /configmap-source
readOnly: true
containers:
- name: server
image: docker.io/library/server:latest
imagePullPolicy: Never
ports:
- containerPort: 3000
name: http01
- containerPort: 8181
name: http02
volumeMounts:
- name: huasen-store
mountPath: /app/huasen-store
- name: server-setting # 可写 emptyDir
mountPath: /app/huasen-server/setting.json
subPath: setting.json
- name: server-config # 只读 ConfigMap
mountPath: /app/huasen-server/config.js
subPath: config.js
- name: huasen-log
mountPath: /app/huasen-server/log
volumes:
- name: huasen-store
persistentVolumeClaim:
claimName: huasen-store
- name: huasen-log
persistentVolumeClaim:
claimName: huasen-log
- name: server-setting # 改用 emptyDir(可写)
emptyDir: {}
- name: server-config # 保持 ConfigMap(只读)
configMap:
name: server-config
- name: configmap-source # 临时的 ConfigMap 挂载(用于 initContainer)
configMap:
name: server-setting
---
apiVersion: v1
kind: Service
metadata:
name: server
namespace: huasenjio
labels:
app: server
spec:
selector:
app: server
ports:
- name: http01
protocol: TCP
port: 3000
targetPort: 3000
- name: http02
protocol: TCP
port: 8181
targetPort: 8181
type: ClusterIP
执行
root@k8s-master01:~/note/huasenjio# kubectl apply -f 05-huasen-server.yaml
configmap/server-setting created
configmap/server-config created
persistentvolumeclaim/huasen-store created
persistentvolumeclaim/huasen-log created
deployment.apps/server created
service/server created
验证
root@k8s-master01:~/note/huasenjio# kubectl get pod -n huasenjio
NAME READY STATUS RESTARTS AGE
mongo-66bb8654d5-446b2 1/1 Running 0 8m59s
nginx-69fb49758c-bwqgh 0/1 CrashLoopBackOff 4 (91s ago) 3m14s
redis-597d744f8-5rbh6 1/1 Running 0 6m5s
server-796cbff887-z4qx4 1/1 Running 0 22s
## 查看日志
root@k8s-master01:~/note/huasenjio# kubectl logs -n huasenjio server-796cbff887-z4qx4
Defaulted container "server" out of: server, init-setting (init)
> server@1.0.0 pm2-in-docker
> pm2-runtime start ecosystem.config.js
2025-04-11T09:52:24: PM2 log: Launching in no daemon mode
2025-04-11T09:52:24: PM2 log: [Watch] Start watching app
2025-04-11T09:52:24: PM2 log: App [app:0] starting in -fork mode-
2025-04-11T09:52:24: PM2 log: App [app:0] online
[Huasen Log]:运行模式 MODE = pro
[Huasen Log]:headHtml请输入
[Huasen Log]:bodyHtml请输入
[Huasen Log]:初始化网站入口失败 Error: EACCES: permission denied, open '/app/huasen-server/public/webapp/portal/index.html'
at Object.openSync (node:fs:590:3)
at Object.writeFileSync (node:fs:2202:35)
at /app/huasen-server/plugin/initialize-site.js:113:8
at Object.<anonymous> (/app/huasen-server/plugin/initialize-site.js:118:3)
at Module._compile (node:internal/modules/cjs/loader:1198:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10)
at Module.load (node:internal/modules/cjs/loader:1076:32)
at Function.Module._load (node:internal/modules/cjs/loader:911:12)
at Module.require (node:internal/modules/cjs/loader:1100:19)
at Module.Hook._require.Module.require (/app/huasen-server/node_modules/require-in-the-middle/index.js:101:39) {
errno: -13,
syscall: 'open',
code: 'EACCES',
path: '/app/huasen-server/public/webapp/portal/index.html'
}
[Huasen Log]:即将连接数据库...
[Huasen Log]:websocket 服务端口为8181
[Huasen Log]:express 服务端口为3000
[Huasen Log]:ioredis 服务端口为6379
[Huasen Log]:mongodb 服务端口为27017
初始化文章成功: {
## 再次检查nginx pod情况,server起来后,运行正常
root@k8s-master01:~/note/huasenjio# kubectl get pod -n huasenjio
NAME READY STATUS RESTARTS AGE
mongo-66bb8654d5-446b2 1/1 Running 0 10m
nginx-69fb49758c-bwqgh 1/1 Running 5 (2m44s ago) 4m27s
redis-597d744f8-5rbh6 1/1 Running 0 7m18s
server-796cbff887-z4qx4 1/1 Running 0 95s
root@k8s-master01:~/note/huasenjio# kubectl logs -n huasenjio nginx-69fb49758c-bwqgh
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
nginx: [warn] conflicting server name "localhost" on 0.0.0.0:80, ignored
2.6、最后验证
查看service
root@k8s-master01:~/note/huasenjio# kubectl get svc -n huasenjio
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mongo ClusterIP None <none> 27017/TCP 21m
nginx LoadBalancer 10.233.47.213 192.168.12.100 80:30195/TCP 15m ## 192.168.12.100 暴露的工作负载ip,可以通过该IP进行访问
redis ClusterIP 10.233.12.19 <none> 6379/TCP 18m
server ClusterIP 10.233.7.3 <none> 3000/TCP,8181/TCP 13m
浏览器访问
http://192.168.12.100/portal/#/home
登录测试
修改重定向,简单配置信息后访问:
2.7、从docker迁移数据到k8s
备份mongo数据
### 进入mongo容器
docker exec -it mongo /bin/
### 备份数据
mongodump --db huasen --out /path/to/save/dump
### 将数据拷贝到物理节点上
docker cp mongo:/path/to/save/dump/huasen /root/.
### 将数据发送到k8smaster节点
scp -r ./huasen/ root@192.168.12.21:/root/
从k8s节点恢复数据
### 复制数据到容器
kubectl cp /root/huasen mongo-66bb8654d5-jh98h:/path/huasen -n huasenjio
### 恢复数据到mongo
mongorestore \
--username="huasenjio" \
--password="Mongo12345*" \
--authenticationDatabase="huasen" \
--db=huasen \
--dir=/path/huasen/
评论区