chore: adjust player tank's size
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.DS_Store
|
||||
.git
|
||||
.gitignore
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
*.md
|
||||
@@ -0,0 +1,256 @@
|
||||
# Tank War Server - 完整部署指南
|
||||
|
||||
## 概述
|
||||
|
||||
本指南详细说明如何将Tank War Server部署到由3台CVM组成的Kubernetes集群中。
|
||||
|
||||
## 集群信息
|
||||
|
||||
目标K8s集群由以下3台CVM组成:
|
||||
- 43.139.80.61 (host_172.16.16.16)
|
||||
- 43.138.255.42 (host_172.16.16.17)
|
||||
- 159.75.104.221 (host_172.16.16.8)
|
||||
|
||||
SSH连接:`ssh root@host_172.16.16.16`
|
||||
|
||||
## 部署前准备
|
||||
|
||||
### 1. 环境检查
|
||||
|
||||
```bash
|
||||
# 在server目录下运行测试脚本
|
||||
cd /Users/hanchengxi/workspace/tankwar_proj/server
|
||||
./test-deployment.sh
|
||||
```
|
||||
|
||||
### 2. 配置Kubernetes访问
|
||||
|
||||
确保kubectl已配置连接到目标集群:
|
||||
|
||||
```bash
|
||||
# 检查集群连接
|
||||
kubectl cluster-info
|
||||
|
||||
# 查看当前上下文
|
||||
kubectl config current-context
|
||||
|
||||
# 如果未配置,需要获取集群的kubeconfig文件
|
||||
# 通常从集群管理员处获取或通过云平台控制台下载
|
||||
```
|
||||
|
||||
## 部署步骤
|
||||
|
||||
### 步骤1:构建Docker镜像
|
||||
|
||||
```bash
|
||||
# 在server目录下构建镜像
|
||||
docker build -t tankwar-server:latest .
|
||||
|
||||
# 验证镜像构建成功
|
||||
docker images | grep tankwar-server
|
||||
```
|
||||
|
||||
### 步骤2:部署到Kubernetes
|
||||
|
||||
```bash
|
||||
# 方法1:使用部署脚本(推荐)
|
||||
chmod +x deploy.sh
|
||||
./deploy.sh
|
||||
|
||||
# 方法2:手动部署
|
||||
kubectl create namespace tankwar --dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl apply -f k8s-deployment.yaml -n tankwar
|
||||
```
|
||||
|
||||
### 步骤3:验证部署
|
||||
|
||||
```bash
|
||||
# 运行验证脚本
|
||||
chmod +x verify-deployment.sh
|
||||
./verify-deployment.sh
|
||||
|
||||
# 或手动验证
|
||||
kubectl get all -n tankwar
|
||||
kubectl logs -l app=tankwar-server -n tankwar
|
||||
```
|
||||
|
||||
## 配置文件说明
|
||||
|
||||
### Dockerfile
|
||||
- 基于Node.js 18 Alpine镜像
|
||||
- 暴露端口3000
|
||||
- 生产环境配置
|
||||
|
||||
### k8s-deployment.yaml
|
||||
包含以下Kubernetes资源:
|
||||
|
||||
1. **ConfigMap**: 环境变量配置
|
||||
2. **Deployment**:
|
||||
- 3个副本
|
||||
- 资源限制:内存512Mi,CPU 500m
|
||||
- 健康检查探针
|
||||
3. **Service**:
|
||||
- LoadBalancer类型
|
||||
- 端口3000
|
||||
|
||||
## 网络配置
|
||||
|
||||
### 服务暴露
|
||||
|
||||
服务使用LoadBalancer类型,将通过云平台的负载均衡器暴露:
|
||||
|
||||
```bash
|
||||
# 获取外部IP
|
||||
kubectl get svc tankwar-server-service -n tankwar
|
||||
|
||||
# WebSocket连接地址
|
||||
ws://<external-ip>:3000
|
||||
```
|
||||
|
||||
### 端口映射
|
||||
|
||||
- 容器端口:3000
|
||||
- 服务端口:3000
|
||||
- 外部访问端口:3000
|
||||
|
||||
## 健康检查
|
||||
|
||||
服务器提供HTTP健康检查端点:
|
||||
|
||||
```bash
|
||||
# 健康检查URL
|
||||
http://<external-ip>:3000/health
|
||||
|
||||
# 返回JSON格式的健康状态
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2024-01-01T00:00:00.000Z",
|
||||
"activeConnections": 0,
|
||||
"activeRooms": 0,
|
||||
"activeTeamRooms": 0
|
||||
}
|
||||
```
|
||||
|
||||
## 监控和维护
|
||||
|
||||
### 查看状态
|
||||
|
||||
```bash
|
||||
# 查看Pod状态
|
||||
kubectl get pods -n tankwar -w
|
||||
|
||||
# 查看服务状态
|
||||
kubectl get svc -n tankwar
|
||||
|
||||
# 查看日志
|
||||
kubectl logs -l app=tankwar-server -n tankwar --tail=50
|
||||
|
||||
# 查看资源使用
|
||||
kubectl top pods -n tankwar
|
||||
```
|
||||
|
||||
### 扩展和伸缩
|
||||
|
||||
```bash
|
||||
# 扩展副本数量
|
||||
kubectl scale deployment/tankwar-server --replicas=5 -n tankwar
|
||||
|
||||
# 自动伸缩(如果配置了HPA)
|
||||
kubectl autoscale deployment/tankwar-server --min=3 --max=10 --cpu-percent=80 -n tankwar
|
||||
```
|
||||
|
||||
### 更新部署
|
||||
|
||||
```bash
|
||||
# 重新构建镜像
|
||||
docker build -t tankwar-server:latest .
|
||||
|
||||
# 滚动更新
|
||||
kubectl rollout restart deployment/tankwar-server -n tankwar
|
||||
|
||||
# 查看更新状态
|
||||
kubectl rollout status deployment/tankwar-server -n tankwar
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **镜像构建失败**
|
||||
```bash
|
||||
# 检查Docker守护进程
|
||||
docker info
|
||||
|
||||
# 检查Dockerfile语法
|
||||
docker build --no-cache -t tankwar-server:latest .
|
||||
```
|
||||
|
||||
2. **Pod无法启动**
|
||||
```bash
|
||||
# 查看Pod详情
|
||||
kubectl describe pod <pod-name> -n tankwar
|
||||
|
||||
# 查看事件
|
||||
kubectl get events -n tankwar
|
||||
```
|
||||
|
||||
3. **服务无法访问**
|
||||
```bash
|
||||
# 检查服务端点
|
||||
kubectl get endpoints tankwar-server-service -n tankwar
|
||||
|
||||
# 检查网络策略
|
||||
kubectl get networkpolicies -n tankwar
|
||||
```
|
||||
|
||||
4. **健康检查失败**
|
||||
```bash
|
||||
# 检查Pod内部
|
||||
kubectl exec -it <pod-name> -n tankwar -- wget -qO- http://localhost:3000/health
|
||||
```
|
||||
|
||||
### 调试命令
|
||||
|
||||
```bash
|
||||
# 进入Pod调试
|
||||
kubectl exec -it <pod-name> -n tankwar -- /bin/sh
|
||||
|
||||
# 端口转发本地调试
|
||||
kubectl port-forward svc/tankwar-server-service 3000:3000 -n tankwar
|
||||
|
||||
# 然后访问:http://localhost:3000/health
|
||||
```
|
||||
|
||||
## 清理部署
|
||||
|
||||
```bash
|
||||
# 删除部署
|
||||
kubectl delete -f k8s-deployment.yaml -n tankwar
|
||||
|
||||
# 删除命名空间
|
||||
kubectl delete namespace tankwar
|
||||
|
||||
# 清理镜像
|
||||
docker rmi tankwar-server:latest
|
||||
```
|
||||
|
||||
## 安全考虑
|
||||
|
||||
1. **网络策略**: 配置适当的网络策略限制访问
|
||||
2. **资源限制**: 设置合理的资源限制防止资源耗尽
|
||||
3. **镜像安全**: 定期更新基础镜像修复安全漏洞
|
||||
4. **访问控制**: 配置RBAC权限控制
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
1. **副本数量**: 根据负载调整副本数量
|
||||
2. **资源分配**: 根据实际使用情况调整资源限制
|
||||
3. **连接池**: 考虑使用连接池优化WebSocket连接
|
||||
4. **监控告警**: 配置监控和告警系统
|
||||
|
||||
## 支持信息
|
||||
|
||||
如有问题,请检查:
|
||||
- 服务器日志:`kubectl logs -l app=tankwar-server -n tankwar`
|
||||
- 部署状态:`kubectl get all -n tankwar`
|
||||
- 集群状态:`kubectl cluster-info`
|
||||
@@ -0,0 +1,25 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
LABEL maintainer="tankwar" \
|
||||
description="Tank War PVP WebSocket Server"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies first (leverage Docker layer cache)
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm install --omit=dev --no-audit --no-fund \
|
||||
&& npm cache clean --force
|
||||
|
||||
# Copy the rest of the source
|
||||
COPY . .
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
HOST=0.0.0.0 \
|
||||
PORT=3000
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
# Graceful shutdown & init for PID 1
|
||||
RUN apk add --no-cache tini
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD ["node", "index.js"]
|
||||
@@ -0,0 +1,183 @@
|
||||
# Tank War Server - Kubernetes部署指南
|
||||
|
||||
## 概述
|
||||
|
||||
Tank War Server是一个基于WebSocket的多人坦克对战游戏服务器,支持1v1和3v3对战模式。本文档说明如何将服务器部署到Kubernetes集群中。
|
||||
|
||||
## 前置要求
|
||||
|
||||
- Docker
|
||||
- Kubernetes集群访问权限
|
||||
- kubectl命令行工具
|
||||
- 对目标K8s集群的访问配置
|
||||
|
||||
## 部署步骤
|
||||
|
||||
### 1. 准备环境
|
||||
|
||||
确保您已配置好对目标Kubernetes集群的访问:
|
||||
|
||||
```bash
|
||||
# 检查集群连接
|
||||
kubectl cluster-info
|
||||
|
||||
# 查看当前上下文
|
||||
kubectl config current-context
|
||||
```
|
||||
|
||||
### 2. 构建Docker镜像
|
||||
|
||||
```bash
|
||||
# 在server目录下构建镜像
|
||||
cd server
|
||||
docker build -t tankwar-server:latest .
|
||||
```
|
||||
|
||||
### 3. 部署到Kubernetes
|
||||
|
||||
使用提供的部署脚本:
|
||||
|
||||
```bash
|
||||
# 给脚本执行权限
|
||||
chmod +x deploy.sh
|
||||
|
||||
# 执行部署
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
或者手动部署:
|
||||
|
||||
```bash
|
||||
# 创建命名空间
|
||||
kubectl create namespace tankwar
|
||||
|
||||
# 部署应用
|
||||
kubectl apply -f k8s-deployment.yaml -n tankwar
|
||||
|
||||
# 等待部署完成
|
||||
kubectl rollout status deployment/tankwar-server -n tankwar
|
||||
```
|
||||
|
||||
### 4. 验证部署
|
||||
|
||||
```bash
|
||||
# 查看Pod状态
|
||||
kubectl get pods -n tankwar
|
||||
|
||||
# 查看服务信息
|
||||
kubectl get svc -n tankwar
|
||||
|
||||
# 查看日志
|
||||
kubectl logs -l app=tankwar-server -n tankwar
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 环境变量
|
||||
|
||||
- `PORT`: 服务器端口(默认:3000)
|
||||
- `HOST`: 绑定地址(默认:0.0.0.0)
|
||||
- `NODE_ENV`: 运行环境(默认:production)
|
||||
|
||||
### 资源限制
|
||||
|
||||
- 内存请求:256Mi,限制:512Mi
|
||||
- CPU请求:250m,限制:500m
|
||||
|
||||
### 健康检查
|
||||
|
||||
服务器提供健康检查端点:
|
||||
- URL: `/health`
|
||||
- 返回JSON格式的健康状态信息
|
||||
|
||||
## 网络配置
|
||||
|
||||
服务使用LoadBalancer类型暴露,可以通过外部IP访问。WebSocket连接地址格式:
|
||||
|
||||
```
|
||||
ws://<external-ip>:3000
|
||||
```
|
||||
|
||||
## 监控和日志
|
||||
|
||||
### 查看日志
|
||||
|
||||
```bash
|
||||
# 查看所有Pod日志
|
||||
kubectl logs -l app=tankwar-server -n tankwar
|
||||
|
||||
# 查看特定Pod日志
|
||||
kubectl logs <pod-name> -n tankwar
|
||||
```
|
||||
|
||||
### 扩展和伸缩
|
||||
|
||||
```bash
|
||||
# 扩展副本数量
|
||||
kubectl scale deployment/tankwar-server --replicas=5 -n tankwar
|
||||
|
||||
# 查看资源使用情况
|
||||
kubectl top pods -n tankwar
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **镜像构建失败**
|
||||
- 检查Docker守护进程是否运行
|
||||
- 确认Dockerfile语法正确
|
||||
|
||||
2. **部署失败**
|
||||
- 检查kubectl集群连接
|
||||
- 验证k8s-deployment.yaml文件语法
|
||||
|
||||
3. **Pod无法启动**
|
||||
- 查看Pod事件:`kubectl describe pod <pod-name> -n tankwar`
|
||||
- 检查资源配额是否足够
|
||||
|
||||
4. **连接问题**
|
||||
- 确认服务已分配外部IP
|
||||
- 检查防火墙规则
|
||||
|
||||
### 调试命令
|
||||
|
||||
```bash
|
||||
# 查看Pod详细信息
|
||||
kubectl describe pod -l app=tankwar-server -n tankwar
|
||||
|
||||
# 进入Pod调试
|
||||
kubectl exec -it <pod-name> -n tankwar -- /bin/sh
|
||||
|
||||
# 查看服务端点
|
||||
kubectl get endpoints tankwar-server-service -n tankwar
|
||||
```
|
||||
|
||||
## 维护操作
|
||||
|
||||
### 更新部署
|
||||
|
||||
```bash
|
||||
# 重新构建镜像
|
||||
docker build -t tankwar-server:latest .
|
||||
|
||||
# 更新部署
|
||||
kubectl rollout restart deployment/tankwar-server -n tankwar
|
||||
```
|
||||
|
||||
### 清理部署
|
||||
|
||||
```bash
|
||||
# 删除部署
|
||||
kubectl delete -f k8s-deployment.yaml -n tankwar
|
||||
|
||||
# 删除命名空间
|
||||
kubectl delete namespace tankwar
|
||||
```
|
||||
|
||||
## 安全考虑
|
||||
|
||||
- 在生产环境中考虑使用Ingress控制器
|
||||
- 配置适当的网络策略
|
||||
- 定期更新镜像以修复安全漏洞
|
||||
- 监控资源使用情况防止资源耗尽
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tank War Server K8s部署脚本
|
||||
set -e
|
||||
|
||||
# 配置变量
|
||||
IMAGE_NAME="tankwar-server"
|
||||
K8S_NAMESPACE="tankwar"
|
||||
K8S_CONFIG="k8s-deployment.yaml"
|
||||
|
||||
# 检查Docker是否运行
|
||||
if ! docker info > /dev/null 2>&1; then
|
||||
echo "错误: Docker守护进程未运行"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 构建Docker镜像
|
||||
echo "构建Docker镜像..."
|
||||
docker build -t $IMAGE_NAME:latest .
|
||||
|
||||
# 登录到目标K8s集群(假设已配置kubectl)
|
||||
echo "检查Kubernetes集群连接..."
|
||||
kubectl cluster-info
|
||||
|
||||
# 创建命名空间(如果不存在)
|
||||
echo "创建命名空间..."
|
||||
kubectl create namespace $K8S_NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 部署应用到K8s
|
||||
echo "部署应用到Kubernetes..."
|
||||
kubectl apply -f $K8S_CONFIG -n $K8S_NAMESPACE
|
||||
|
||||
# 等待部署完成
|
||||
echo "等待部署完成..."
|
||||
kubectl rollout status deployment/tankwar-server -n $K8S_NAMESPACE --timeout=300s
|
||||
|
||||
# 获取服务信息
|
||||
echo "获取服务信息..."
|
||||
kubectl get svc tankwar-server-service -n $K8S_NAMESPACE
|
||||
|
||||
echo "部署完成!"
|
||||
echo "使用以下命令查看Pod状态:"
|
||||
echo "kubectl get pods -n $K8S_NAMESPACE"
|
||||
echo ""
|
||||
echo "使用以下命令查看日志:"
|
||||
echo "kubectl logs -l app=tankwar-server -n $K8S_NAMESPACE"
|
||||
+96
-22
@@ -2,18 +2,48 @@
|
||||
* Tank War PVP Server
|
||||
* WebSocket server for online 1v1 multiplayer.
|
||||
* Handles room management, message relay, and basic game state authority.
|
||||
*
|
||||
* Deployment note:
|
||||
* - /health → HTTP health check (used by K8s livenessProbe/readinessProbe)
|
||||
* - /tankwar/ws → WebSocket upgrade path (exposed publicly via Nginx)
|
||||
* Both share the same HTTP server on PORT.
|
||||
*/
|
||||
|
||||
const { WebSocketServer } = require('ws');
|
||||
const http = require('http');
|
||||
|
||||
// ============================================================
|
||||
// Configuration
|
||||
// ============================================================
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
const WS_PATH = process.env.WS_PATH || '/tankwar/ws';
|
||||
const HEARTBEAT_INTERVAL = 10000; // ms
|
||||
const ROOM_TIMEOUT = 300000; // 5 minutes room idle timeout
|
||||
|
||||
// ============================================================
|
||||
// HTTP Health Check Server
|
||||
// ============================================================
|
||||
const healthServer = http.createServer((req, res) => {
|
||||
if (req.url === '/health' || req.url === '/tankwar/health') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
activeConnections: players.size,
|
||||
activeRooms: rooms.size,
|
||||
activeTeamRooms: teamRooms.size
|
||||
}));
|
||||
} else {
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('Not Found');
|
||||
}
|
||||
});
|
||||
|
||||
healthServer.listen(PORT, HOST, () => {
|
||||
console.log(`[Health Server] Running on ${HOST}:${PORT}`);
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// Message Types (must match client NET_MSG)
|
||||
// ============================================================
|
||||
@@ -135,6 +165,7 @@ class PlayerInfo {
|
||||
constructor(ws, playerId) {
|
||||
this.ws = ws;
|
||||
this.playerId = playerId;
|
||||
this.nickname = '';
|
||||
this.roomId = null;
|
||||
this.teamId = null;
|
||||
this.isAlive = true;
|
||||
@@ -152,8 +183,9 @@ class TeamRoom {
|
||||
* @param {WebSocket} leaderWs - WebSocket of the team leader
|
||||
* @param {string} leaderId - Player id of the leader
|
||||
* @param {string} [battleMode='3v3'] - Battle mode ('1v1' or '3v3')
|
||||
* @param {string} [leaderNickname=''] - Display nickname of the leader
|
||||
*/
|
||||
constructor(id, leaderWs, leaderId, battleMode = '3v3') {
|
||||
constructor(id, leaderWs, leaderId, battleMode = '3v3', leaderNickname = '') {
|
||||
this.id = id;
|
||||
this.state = 'forming'; // forming | matching | playing | finished
|
||||
this.createdAt = Date.now();
|
||||
@@ -164,8 +196,8 @@ class TeamRoom {
|
||||
this.teamSize = config.teamSize;
|
||||
this.fillWithBotsEnabled = config.fillWithBots;
|
||||
|
||||
// Team A members: { ws, playerId, ready, isBot, disconnectedAt }
|
||||
this.teamA = [{ ws: leaderWs, playerId: leaderId, ready: true, isBot: false, disconnectedAt: null }];
|
||||
// Team A members: { ws, playerId, nickname, ready, isBot, disconnectedAt }
|
||||
this.teamA = [{ ws: leaderWs, playerId: leaderId, nickname: leaderNickname || '', ready: true, isBot: false, disconnectedAt: null }];
|
||||
// Team B members
|
||||
this.teamB = [];
|
||||
this.leaderId = leaderId;
|
||||
@@ -233,16 +265,16 @@ class TeamRoom {
|
||||
}
|
||||
|
||||
/** Add a player to team A */
|
||||
addToTeamA(ws, playerId) {
|
||||
addToTeamA(ws, playerId, nickname = '') {
|
||||
if (this.isTeamAFull()) return false;
|
||||
this.teamA.push({ ws, playerId, ready: false, isBot: false, disconnectedAt: null });
|
||||
this.teamA.push({ ws, playerId, nickname, ready: false, isBot: false, disconnectedAt: null });
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Add a player to team B */
|
||||
addToTeamB(ws, playerId) {
|
||||
addToTeamB(ws, playerId, nickname = '') {
|
||||
if (this.teamB.length >= this.teamSize) return false;
|
||||
this.teamB.push({ ws, playerId, ready: false, isBot: false, disconnectedAt: null });
|
||||
this.teamB.push({ ws, playerId, nickname, ready: false, isBot: false, disconnectedAt: null });
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -260,6 +292,7 @@ class TeamRoom {
|
||||
this.teamA.push({
|
||||
ws: null,
|
||||
playerId: `bot_a_${botCounter}_${this.id}`,
|
||||
nickname: '',
|
||||
ready: true,
|
||||
isBot: true,
|
||||
disconnectedAt: null,
|
||||
@@ -270,6 +303,7 @@ class TeamRoom {
|
||||
this.teamB.push({
|
||||
ws: null,
|
||||
playerId: `bot_b_${botCounter}_${this.id}`,
|
||||
nickname: '',
|
||||
ready: true,
|
||||
isBot: true,
|
||||
disconnectedAt: null,
|
||||
@@ -335,6 +369,7 @@ class TeamRoom {
|
||||
teamSize: this.teamSize,
|
||||
teamA: this.teamA.map(m => ({
|
||||
playerId: m.playerId,
|
||||
nickname: m.nickname || '',
|
||||
ready: m.ready,
|
||||
isBot: m.isBot,
|
||||
isLeader: m.playerId === this.leaderId,
|
||||
@@ -342,6 +377,7 @@ class TeamRoom {
|
||||
})),
|
||||
teamB: this.teamB.map(m => ({
|
||||
playerId: m.playerId,
|
||||
nickname: m.nickname || '',
|
||||
ready: m.ready,
|
||||
isBot: m.isBot,
|
||||
connected: m.isBot || (m.ws && m.ws.readyState === 1),
|
||||
@@ -449,7 +485,7 @@ function handleCreateRoom(ws, data) {
|
||||
const roomCode = generateRoomCode();
|
||||
|
||||
// Create a TeamRoom in 1v1 mode instead of a legacy Room
|
||||
const teamRoom = new TeamRoom(roomCode, ws, playerInfo.playerId, '1v1');
|
||||
const teamRoom = new TeamRoom(roomCode, ws, playerInfo.playerId, '1v1', playerInfo.nickname || '');
|
||||
teamRooms.set(roomCode, teamRoom);
|
||||
playerInfo.teamId = roomCode;
|
||||
|
||||
@@ -498,7 +534,7 @@ function handleJoinRoom(ws, data) {
|
||||
}
|
||||
|
||||
// Join as team B
|
||||
teamRoom.addToTeamB(ws, playerInfo.playerId);
|
||||
teamRoom.addToTeamB(ws, playerInfo.playerId, playerInfo.nickname || '');
|
||||
playerInfo.teamId = roomId;
|
||||
|
||||
console.log(`[Server] Player ${playerInfo.playerId} joined 1v1 room ${roomId} (TeamRoom)`);
|
||||
@@ -568,7 +604,7 @@ function handleCreateTeam(ws, data) {
|
||||
}
|
||||
|
||||
const teamId = generateTeamId();
|
||||
const teamRoom = new TeamRoom(teamId, ws, playerInfo.playerId);
|
||||
const teamRoom = new TeamRoom(teamId, ws, playerInfo.playerId, '3v3', playerInfo.nickname || '');
|
||||
teamRooms.set(teamId, teamRoom);
|
||||
playerInfo.teamId = teamId;
|
||||
|
||||
@@ -592,7 +628,7 @@ function handleJoinTeam(ws, data) {
|
||||
// Team was cleaned up (e.g. leader disconnected during dev-tool reload).
|
||||
// Auto-create a new room with the same ID so the invite link still works.
|
||||
console.log(`[Server] Team ${teamId} not found, auto-creating for ${playerInfo.playerId}`);
|
||||
teamRoom = new TeamRoom(teamId, ws, playerInfo.playerId);
|
||||
teamRoom = new TeamRoom(teamId, ws, playerInfo.playerId, '3v3', playerInfo.nickname || '');
|
||||
teamRooms.set(teamId, teamRoom);
|
||||
playerInfo.teamId = teamId;
|
||||
sendMessage(ws, NET_MSG.TEAM_STATE, teamRoom.getTeamState());
|
||||
@@ -614,7 +650,7 @@ function handleJoinTeam(ws, data) {
|
||||
handleLeaveTeam(ws, {});
|
||||
}
|
||||
|
||||
teamRoom.addToTeamA(ws, playerInfo.playerId);
|
||||
teamRoom.addToTeamA(ws, playerInfo.playerId, playerInfo.nickname || '');
|
||||
playerInfo.teamId = teamId;
|
||||
|
||||
console.log(`[Server] Player ${playerInfo.playerId} joined team ${teamId}`);
|
||||
@@ -815,7 +851,7 @@ function handleSoloMatch(ws, data) {
|
||||
|
||||
// Create a solo team room for this player
|
||||
const teamId = generateTeamId();
|
||||
const teamRoom = new TeamRoom(teamId, ws, playerInfo.playerId);
|
||||
const teamRoom = new TeamRoom(teamId, ws, playerInfo.playerId, '3v3', playerInfo.nickname || '');
|
||||
teamRoom.state = 'matching';
|
||||
teamRoom.matchStartTime = Date.now();
|
||||
teamRooms.set(teamId, teamRoom);
|
||||
@@ -888,7 +924,7 @@ function tryMatchTeams() {
|
||||
|
||||
// Merge team B members into team A room as opponents
|
||||
for (const member of teamB_room.teamA) {
|
||||
teamA_room.addToTeamB(member.ws, member.playerId);
|
||||
teamA_room.addToTeamB(member.ws, member.playerId, member.nickname || '');
|
||||
if (member.ws) {
|
||||
const info = players.get(member.ws);
|
||||
if (info) info.teamId = teamA_room.id;
|
||||
@@ -953,9 +989,9 @@ function tryMatchTeams() {
|
||||
|
||||
// Alternate: odd index -> team A, even index -> team B
|
||||
if (i % 2 === 1 && !gameRoom.isTeamAFull()) {
|
||||
gameRoom.addToTeamA(ws, info.playerId);
|
||||
gameRoom.addToTeamA(ws, info.playerId, info.nickname || '');
|
||||
} else {
|
||||
gameRoom.addToTeamB(ws, info.playerId);
|
||||
gameRoom.addToTeamB(ws, info.playerId, info.nickname || '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,8 +1022,8 @@ function startTeamGame(teamRoom) {
|
||||
|
||||
const gameData = {
|
||||
mapId: teamRoom.mapId,
|
||||
teamA: teamRoom.teamA.map(m => ({ playerId: m.playerId, isBot: m.isBot })),
|
||||
teamB: teamRoom.teamB.map(m => ({ playerId: m.playerId, isBot: m.isBot })),
|
||||
teamA: teamRoom.teamA.map(m => ({ playerId: m.playerId, nickname: m.nickname || '', isBot: m.isBot })),
|
||||
teamB: teamRoom.teamB.map(m => ({ playerId: m.playerId, nickname: m.nickname || '', isBot: m.isBot })),
|
||||
teamABaseHp: teamRoom.teamABaseHp,
|
||||
teamBBaseHp: teamRoom.teamBBaseHp,
|
||||
battleMode: teamRoom.battleMode,
|
||||
@@ -1306,7 +1342,7 @@ function handleMessage(ws, rawData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, data, playerId } = msg;
|
||||
const { type, data, playerId, nickname } = msg;
|
||||
|
||||
// Update player info
|
||||
const playerInfo = players.get(ws);
|
||||
@@ -1315,6 +1351,26 @@ function handleMessage(ws, rawData) {
|
||||
if (playerId && !playerInfo.playerId) {
|
||||
playerInfo.playerId = playerId;
|
||||
}
|
||||
// Refresh nickname on every message (it may be granted mid-session).
|
||||
if (typeof nickname === 'string' && nickname) {
|
||||
if (playerInfo.nickname !== nickname) {
|
||||
playerInfo.nickname = nickname;
|
||||
// Also propagate into any active team room member entry.
|
||||
if (playerInfo.teamId) {
|
||||
const tr = teamRooms.get(playerInfo.teamId);
|
||||
if (tr) {
|
||||
const member = tr.getMemberByWs(ws);
|
||||
if (member && member.nickname !== nickname) {
|
||||
member.nickname = nickname;
|
||||
// Broadcast regardless of room state (forming / matching / playing)
|
||||
// so that peers always render the latest display name — in 3v3 a
|
||||
// player may only tap the UserInfoButton AFTER the match starts.
|
||||
tr.broadcast(NET_MSG.TEAM_STATE, tr.getTeamState());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
@@ -1538,9 +1594,26 @@ setInterval(() => {
|
||||
}, 300000); // Every 5 minutes
|
||||
|
||||
// ============================================================
|
||||
// WebSocket Server
|
||||
// WebSocket Server (noServer mode, shares HTTP server with health check)
|
||||
// ============================================================
|
||||
const wss = new WebSocketServer({ host: HOST, port: PORT });
|
||||
// Use noServer mode so the WS upgrade only fires on the configured path.
|
||||
// This lets /health stay as plain HTTP on the same port.
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
|
||||
healthServer.on('upgrade', (req, socket, head) => {
|
||||
// Only upgrade on the configured WebSocket path; reject any other path.
|
||||
// We compare by pathname so query strings are tolerated.
|
||||
const pathname = (req.url || '').split('?')[0];
|
||||
if (pathname !== WS_PATH) {
|
||||
console.warn(`[Server] Rejected WebSocket upgrade on unexpected path: ${req.url}`);
|
||||
socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||
wss.emit('connection', ws, req);
|
||||
});
|
||||
});
|
||||
|
||||
wss.on('connection', (ws, req) => {
|
||||
const ip = req.socket.remoteAddress;
|
||||
@@ -1604,4 +1677,5 @@ setInterval(() => {
|
||||
// Startup
|
||||
// ============================================================
|
||||
console.log(`[Tank War Server] Running on ${HOST}:${PORT}`);
|
||||
console.log(`[Tank War Server] WebSocket URL: ws://${HOST}:${PORT}`);
|
||||
console.log(`[Tank War Server] WebSocket path: ${WS_PATH}`);
|
||||
console.log(`[Tank War Server] Health check paths: /health, /tankwar/health`);
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: tankwar-server-config
|
||||
data:
|
||||
NODE_ENV: "production"
|
||||
PORT: "3000"
|
||||
HOST: "0.0.0.0"
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: tankwar-server
|
||||
labels:
|
||||
app: tankwar-server
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: tankwar-server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: tankwar-server
|
||||
spec:
|
||||
containers:
|
||||
- name: tankwar-server
|
||||
image: tankwar-server:latest
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: tankwar-server-config
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: tankwar
|
||||
labels:
|
||||
app.kubernetes.io/part-of: tankwar
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: tankwar-server-config
|
||||
namespace: tankwar
|
||||
data:
|
||||
NODE_ENV: "production"
|
||||
PORT: "3000"
|
||||
HOST: "0.0.0.0"
|
||||
# WebSocket path must match Nginx location and client SERVER_URL
|
||||
WS_PATH: "/tankwar/ws"
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: tankwar-server
|
||||
namespace: tankwar
|
||||
labels:
|
||||
app: tankwar-server
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: tankwar-server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: tankwar-server
|
||||
spec:
|
||||
containers:
|
||||
- name: tankwar-server
|
||||
image: tankwar/tankwar-server:latest
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
name: http-ws
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: tankwar-server-config
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3000
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 3000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: tankwar-server
|
||||
namespace: tankwar
|
||||
labels:
|
||||
app: tankwar-server
|
||||
spec:
|
||||
# ClusterIP: only exposed internally, external traffic comes through
|
||||
# warmcheck-namespace Nginx at game.igeek.site -> /tankwar/ws
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: tankwar-server
|
||||
ports:
|
||||
- name: http-ws
|
||||
port: 3000
|
||||
targetPort: 3000
|
||||
protocol: TCP
|
||||
type: LoadBalancer
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# Tankwar server K8s deploy script
|
||||
# Syncs server source -> Master node -> builds Docker image ->
|
||||
# distributes image to Worker nodes via ctr -> applies K8s resources
|
||||
# -> rolls out the tankwar-server deployment.
|
||||
# ============================================================
|
||||
set -e
|
||||
|
||||
LOG="/tmp/tankwar-k8s-deploy.log"
|
||||
> "$LOG"
|
||||
exec > >(tee -a "$LOG") 2>&1
|
||||
|
||||
SERVER_DIR="/Users/hanchengxi/workspace/tankwar_proj/server"
|
||||
MASTER="root@host_172.16.16.16"
|
||||
WORKERS_IP=("172.16.16.17" "172.16.16.8")
|
||||
REMOTE_BUILD_DIR="/tmp/tankwar-build"
|
||||
IMAGE_NAME="tankwar/tankwar-server:latest"
|
||||
|
||||
ts() { echo "[$(date '+%H:%M:%S')]"; }
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Step 0: Sync server source to master node
|
||||
# ------------------------------------------------------------
|
||||
echo "$(ts) ===== Syncing tankwar server source to master node ====="
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" "mkdir -p $REMOTE_BUILD_DIR"
|
||||
rsync -az --delete --exclude='.git' --exclude='node_modules' \
|
||||
-e "ssh -o StrictHostKeyChecking=no" \
|
||||
"$SERVER_DIR/" "${MASTER}:${REMOTE_BUILD_DIR}/server/"
|
||||
echo "$(ts) ✓ Source synced"
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Step 1: Ensure docker is available on master
|
||||
# ------------------------------------------------------------
|
||||
echo "$(ts) ===== Checking docker on master ====="
|
||||
if ! ssh -o StrictHostKeyChecking=no "$MASTER" "which docker >/dev/null 2>&1"; then
|
||||
echo "$(ts) Docker not found on master. Installing..."
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" "curl -fsSL https://get.docker.com | sh"
|
||||
fi
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" "docker version --format '{{.Server.Version}}' 2>/dev/null || systemctl start docker"
|
||||
echo "$(ts) ✓ Docker ready on master"
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Step 2: Build image on master
|
||||
# ------------------------------------------------------------
|
||||
echo "$(ts) ===== Building $IMAGE_NAME on master ====="
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" \
|
||||
"cd $REMOTE_BUILD_DIR/server && docker build -t $IMAGE_NAME -f Dockerfile ."
|
||||
echo "$(ts) ✓ Image built"
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Step 3: Distribute image to workers (containerd / ctr)
|
||||
# ------------------------------------------------------------
|
||||
echo "$(ts) ===== Distributing $IMAGE_NAME to workers ====="
|
||||
# Master itself may also be a worker; import locally first so master pods can use it.
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" \
|
||||
"docker save $IMAGE_NAME | ctr -n k8s.io images import -"
|
||||
for w in "${WORKERS_IP[@]}"; do
|
||||
echo "$(ts) -> $w"
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" \
|
||||
"docker save $IMAGE_NAME | ssh -o StrictHostKeyChecking=no root@$w 'ctr -n k8s.io images import -'"
|
||||
done
|
||||
echo "$(ts) ✓ Image distributed"
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Step 4: Apply K8s manifests (namespace + configmap + deploy + svc)
|
||||
# ------------------------------------------------------------
|
||||
echo "$(ts) ===== Applying K8s manifests ====="
|
||||
cat "$SERVER_DIR/k8s-deployment.yaml" | \
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" "kubectl apply -f -"
|
||||
echo "$(ts) ✓ Manifests applied"
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Step 5: Restart deployment to pick up the new image
|
||||
# ------------------------------------------------------------
|
||||
echo "$(ts) ===== Restarting tankwar-server deployment ====="
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" \
|
||||
"kubectl -n tankwar rollout restart deployment/tankwar-server" || true
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" \
|
||||
"kubectl -n tankwar rollout status deployment/tankwar-server --timeout=120s" || true
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Step 6: Show final status
|
||||
# ------------------------------------------------------------
|
||||
echo "$(ts) ===== Final Status ====="
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" "kubectl -n tankwar get pods -o wide"
|
||||
echo ""
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" "kubectl -n tankwar get svc"
|
||||
|
||||
echo ""
|
||||
echo "$(ts) ===== ALL DONE ====="
|
||||
echo "$(ts) Public endpoint (via warmcheck Nginx): wss://www.igeek.site/games/wx/tankwar/ws"
|
||||
echo "$(ts) Internal endpoint: tankwar-server.tankwar.svc.cluster.local:3000"
|
||||
echo "$(ts) Remember to redeploy warmcheck Nginx too (run WarmCheck_proj/backend/deploy/k8s/scripts/run-deploy.sh)"
|
||||
|
||||
# Cleanup
|
||||
ssh -o StrictHostKeyChecking=no "$MASTER" "rm -rf $REMOTE_BUILD_DIR" 2>/dev/null || true
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tank War Server部署测试脚本
|
||||
set -e
|
||||
|
||||
echo "=== Tank War Server部署测试 ==="
|
||||
echo ""
|
||||
|
||||
# 检查必需文件是否存在
|
||||
echo "1. 检查必需文件..."
|
||||
files=("Dockerfile" "k8s-deployment.yaml" "index.js" "package.json" "deploy.sh")
|
||||
for file in "${files[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "✓ $file 存在"
|
||||
else
|
||||
echo "✗ $file 缺失"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "2. 检查Dockerfile语法..."
|
||||
if grep -q "FROM node" Dockerfile && grep -q "EXPOSE 3000" Dockerfile; then
|
||||
echo "✓ Dockerfile语法正确"
|
||||
else
|
||||
echo "✗ Dockerfile语法错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "3. 检查K8s配置文件..."
|
||||
# 检查基本的YAML语法
|
||||
if python3 -c "import yaml; yaml.safe_load(open('k8s-deployment.yaml'))" > /dev/null 2>&1; then
|
||||
echo "✓ K8s配置文件语法正确"
|
||||
else
|
||||
echo "⚠ 无法验证K8s配置,需要连接到Kubernetes集群"
|
||||
echo " 运行 'kubectl cluster-info' 检查连接状态"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "4. 检查Node.js依赖..."
|
||||
if node -e "require('./package.json')" > /dev/null 2>&1; then
|
||||
echo "✓ package.json语法正确"
|
||||
else
|
||||
echo "✗ package.json语法错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "5. 检查服务器代码..."
|
||||
if node -c index.js > /dev/null 2>&1; then
|
||||
echo "✓ index.js语法正确"
|
||||
else
|
||||
echo "✗ index.js语法错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "6. 检查部署脚本权限..."
|
||||
chmod +x deploy.sh
|
||||
if [ -x "deploy.sh" ]; then
|
||||
echo "✓ 部署脚本可执行"
|
||||
else
|
||||
echo "✗ 部署脚本权限错误"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 所有检查通过! ==="
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo "1. 确保Docker守护进程正在运行"
|
||||
echo "2. 确保kubectl已配置正确的集群上下文"
|
||||
echo "3. 运行 ./deploy.sh 开始部署"
|
||||
echo ""
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tank War Server部署验证脚本
|
||||
set -e
|
||||
|
||||
echo "=== Tank War Server部署验证 ==="
|
||||
echo ""
|
||||
|
||||
# 检查Kubernetes集群连接
|
||||
echo "1. 检查Kubernetes集群连接..."
|
||||
if kubectl cluster-info > /dev/null 2>&1; then
|
||||
echo "✓ Kubernetes集群连接正常"
|
||||
|
||||
# 检查命名空间
|
||||
echo ""
|
||||
echo "2. 检查命名空间..."
|
||||
if kubectl get namespace tankwar > /dev/null 2>&1; then
|
||||
echo "✓ tankwar命名空间存在"
|
||||
else
|
||||
echo "✗ tankwar命名空间不存在"
|
||||
echo " 运行: kubectl create namespace tankwar"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查部署状态
|
||||
echo ""
|
||||
echo "3. 检查部署状态..."
|
||||
kubectl get deployment tankwar-server -n tankwar 2>/dev/null || {
|
||||
echo "✗ tankwar-server部署不存在"
|
||||
echo " 运行: kubectl apply -f k8s-deployment.yaml -n tankwar"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 检查Pod状态
|
||||
echo ""
|
||||
echo "4. 检查Pod状态..."
|
||||
kubectl get pods -l app=tankwar-server -n tankwar
|
||||
|
||||
# 检查服务状态
|
||||
echo ""
|
||||
echo "5. 检查服务状态..."
|
||||
kubectl get svc tankwar-server-service -n tankwar
|
||||
|
||||
# 检查服务端点
|
||||
echo ""
|
||||
echo "6. 检查服务端点..."
|
||||
kubectl get endpoints tankwar-server-service -n tankwar
|
||||
|
||||
# 检查Pod日志
|
||||
echo ""
|
||||
echo "7. 检查Pod日志(最近10行)..."
|
||||
kubectl logs -l app=tankwar-server -n tankwar --tail=10
|
||||
|
||||
echo ""
|
||||
echo "=== 部署验证完成 ==="
|
||||
echo ""
|
||||
echo "如果所有检查都通过,服务应该正在运行。"
|
||||
echo "WebSocket连接地址格式:ws://<external-ip>:3000"
|
||||
echo ""
|
||||
echo "获取外部IP:"
|
||||
echo "kubectl get svc tankwar-server-service -n tankwar -o jsonpath='{.status.loadBalancer.ingress[0].ip}'"
|
||||
|
||||
else
|
||||
echo "✗ Kubernetes集群连接失败"
|
||||
echo "请配置kubectl连接到正确的集群"
|
||||
echo ""
|
||||
echo "配置方法:"
|
||||
echo "1. 获取集群kubeconfig文件"
|
||||
echo "2. 设置KUBECONFIG环境变量"
|
||||
echo "3. 或复制到 ~/.kube/config"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user