Compare commits
4 Commits
feature/de
...
docs/kuber
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a12c0eca04 | ||
|
|
a65e368522 | ||
|
|
4a0b04af17 | ||
|
|
8c98fc96d7 |
23
Makefile
23
Makefile
@@ -917,31 +917,12 @@ generate-manpage: ## generate manpage
|
|||||||
|
|
||||||
.PHONY: devstar
|
.PHONY: devstar
|
||||||
devstar:
|
devstar:
|
||||||
@if docker pull devstar.cn/devstar/devstar-dev-container:v1.0; then \
|
|
||||||
docker tag devstar.cn/devstar/devstar-dev-container:v1.0 devstar.cn/devstar/devstar-dev-container:latest && \
|
|
||||||
echo "Successfully pulled devstar.cn/devstar/devstar-dev-container:v1.0 taged to latest"; \
|
|
||||||
else \
|
|
||||||
docker build -t devstar.cn/devstar/devstar-dev-container:latest -f docker/Dockerfile.devContainer . && \
|
|
||||||
echo "Successfully build devstar.cn/devstar/devstar-dev-container:latest"; \
|
|
||||||
fi
|
|
||||||
@if docker pull devstar.cn/devstar/devstar-runtime-container:v1.0; then \
|
|
||||||
docker tag devstar.cn/devstar/devstar-runtime-container:v1.0 devstar.cn/devstar/devstar-runtime-container:latest && \
|
|
||||||
echo "Successfully pulled devstar.cn/devstar/devstar-runtime-container:v1.0 taged to latest"; \
|
|
||||||
else \
|
|
||||||
docker build -t devstar.cn/devstar/devstar-runtime-container:latest -f docker/Dockerfile.runtimeContainer . && \
|
|
||||||
echo "Successfully build devstar.cn/devstar/devstar-runtime-container:latest"; \
|
|
||||||
fi
|
|
||||||
@if docker pull devstar.cn/devstar/webterminal:v1.0; then \
|
|
||||||
docker tag devstar.cn/devstar/webterminal:v1.0 devstar.cn/devstar/webterminal:latest && \
|
|
||||||
echo "Successfully pulled devstar.cn/devstar/webterminal:v1.0 taged to latest"; \
|
|
||||||
else \
|
|
||||||
docker build --no-cache -t devstar.cn/devstar/webterminal:latest -f docker/Dockerfile.webTerminal . && \
|
|
||||||
echo "Successfully build devstar.cn/devstar/devstar-runtime-container:latest"; \
|
|
||||||
fi
|
|
||||||
docker build -t devstar-studio:latest -f docker/Dockerfile.devstar .
|
docker build -t devstar-studio:latest -f docker/Dockerfile.devstar .
|
||||||
|
|
||||||
.PHONY: docker
|
.PHONY: docker
|
||||||
docker:
|
docker:
|
||||||
|
docker build -t devstar.cn/devstar/webterminal:latest -f docker/Dockerfile.webTerminal .
|
||||||
|
|
||||||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
||||||
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
|
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,6 @@ RUN apk --no-cache add \
|
|||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
# To acquire Gitea dev container:
|
# To acquire Gitea dev container:
|
||||||
# $ docker build -t devstar.cn/devstar/devstar-dev-container:v1.0 -f docker/Dockerfile.devContainer .
|
# $ docker build -t devstar.cn/devstar/devstar-dev-container:latest -f docker/Dockerfile.devContainer .
|
||||||
# $ docker login devstar.cn
|
# $ docker login devstar.cn
|
||||||
# $ docker push devstar.cn/devstar/devstar-dev-container:v1.0
|
|
||||||
# $ docker tag devstar.cn/devstar/devstar-dev-container:v1.0 devstar.cn/devstar/devstar-dev-container:latest
|
|
||||||
# $ docker push devstar.cn/devstar/devstar-dev-container:latest
|
# $ docker push devstar.cn/devstar/devstar-dev-container:latest
|
||||||
|
|
||||||
|
|
||||||
# Release Notes:
|
|
||||||
# v1.0 - Initial release
|
|
||||||
|
|||||||
@@ -89,14 +89,3 @@ WORKDIR /var/lib/gitea
|
|||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/docker-entrypoint.sh"]
|
||||||
CMD []
|
CMD []
|
||||||
|
|
||||||
# To acquire devstar.cn/devstar/devstar-studio:latest:
|
|
||||||
# $ docker build -t devstar-studio:latest -f docker/Dockerfile.devstar .
|
|
||||||
# $ docker login devstar.cn
|
|
||||||
# $ docker tag devstar-studio:latest devstar.cn/devstar/devstar-studio:latest
|
|
||||||
# $ docker tag devstar-studio:latest devstar.cn/devstar/devstar-studio:v1.0
|
|
||||||
# $ docker push devstar.cn/devstar/devstar-studio:latest
|
|
||||||
# $ docker push devstar.cn/devstar/devstar-studio:v1.0
|
|
||||||
|
|
||||||
# Release Notes:
|
|
||||||
# v1.0 - Initial release
|
|
||||||
|
|||||||
@@ -19,12 +19,6 @@ RUN apk --no-cache add \
|
|||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
# To acquire Gitea base runtime container:
|
# To acquire Gitea base runtime container:
|
||||||
# $ docker build -t devstar.cn/devstar/devstar-runtime-container:v1.0 -f docker/Dockerfile.runtimeContainer .
|
# $ docker build -t devstar.cn/devstar/devstar-runtime-container:latest -f docker/Dockerfile.runtimeContainer .
|
||||||
# $ docker login devstar.cn
|
# $ docker login devstar.cn
|
||||||
# $ docker push devstar.cn/devstar/devstar-runtime-container:v1.0
|
|
||||||
# $ docker tag devstar.cn/devstar/devstar-runtime-container:v1.0 devstar.cn/devstar/devstar-runtime-container:latest
|
|
||||||
# $ docker push devstar.cn/devstar/devstar-runtime-container:latest
|
# $ docker push devstar.cn/devstar/devstar-runtime-container:latest
|
||||||
|
|
||||||
|
|
||||||
# Release Notes:
|
|
||||||
# v1.0 - Initial release
|
|
||||||
|
|||||||
@@ -38,13 +38,3 @@ RUN apt-get update && \
|
|||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||||
CMD ["/home/webTerminal/build/ttyd", "-W", "bash"]
|
CMD ["/home/webTerminal/build/ttyd", "-W", "bash"]
|
||||||
|
|
||||||
# To acquire devstar.cn/devstar/webterminal:latest:
|
|
||||||
# $ docker build --no-cache -t devstar.cn/devstar/webterminal:v1.0 -f docker/Dockerfile.webTerminal .
|
|
||||||
# $ docker login devstar.cn
|
|
||||||
# $ docker push devstar.cn/devstar/webterminal:v1.0
|
|
||||||
# $ docker tag devstar.cn/devstar/webterminal:v1.0 devstar.cn/devstar/webterminal:latest
|
|
||||||
# $ docker push devstar.cn/devstar/webterminal:latest
|
|
||||||
|
|
||||||
# Release Notes:
|
|
||||||
# v1.0 - Initial release
|
|
||||||
87
docs/kubernetes/README.md
Normal file
87
docs/kubernetes/README.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
## Kubernetes 文档入口
|
||||||
|
|
||||||
|
本目录提供从零到一的 Kubernetes 集群安装与常用脚本。建议:先阅读本 README 的概览与快速开始,再按需查看详细版文档。
|
||||||
|
|
||||||
|
### 文档索引
|
||||||
|
|
||||||
|
- **Kubernetes 安装**:`k8s-installtion.md`(分步说明、完整命令与排错)
|
||||||
|
- **Istio 配置**:`istio-hostnetwork-notes.md`(将 Istio IngressGateway 切换为 hostNetwork 模式指南)
|
||||||
|
|
||||||
|
### 快速开始
|
||||||
|
|
||||||
|
在 Master 节点:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./k8s-step1-prepare-env.sh
|
||||||
|
./k8s-step2-install-containerd.sh
|
||||||
|
./k8s-step3-install-components.sh
|
||||||
|
./k8s-step4-init-cluster.sh
|
||||||
|
./k8s-step5-install-flannel.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
在工作节点加入(参考 `node-join-command.txt` 或运行第 6 步脚本):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./k8s-step6-join-nodes.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl get nodes -o wide
|
||||||
|
kubectl get pods -A
|
||||||
|
```
|
||||||
|
|
||||||
|
### 脚本总览
|
||||||
|
|
||||||
|
- 安装流程
|
||||||
|
- `k8s-step1-prepare-env.sh`:环境准备(关闭 swap、内核参数、基础工具)
|
||||||
|
- `k8s-step2-install-containerd.sh`:安装与配置 containerd
|
||||||
|
- `k8s-step3-install-components.sh`:安装 kubeadm/kubelet/kubectl
|
||||||
|
- `k8s-step4-init-cluster.sh`:Master 初始化集群
|
||||||
|
- `k8s-step5-install-flannel.sh`:安装 Flannel CNI(或直接 `kubectl apply -f kube-flannel.yml`)
|
||||||
|
- `k8s-step6-join-nodes.sh`:节点加入集群(使用 `node-join-command.txt`)
|
||||||
|
- `k8s-install-all.sh`:一键顺序执行上述步骤(熟悉流程后使用)
|
||||||
|
|
||||||
|
- 网络与工具
|
||||||
|
- `setup-master-gateway.sh`:Master 网关/NAT 示例配置(按需修改)
|
||||||
|
- `setup-node1.sh`、`setup-node2.sh`:节点路由示例
|
||||||
|
- `k8s-image-pull-and-import.sh`:镜像预拉取/导入(离线或网络慢场景)
|
||||||
|
- `install-kubectl-nodes.sh`:为其他节点安装与配置 kubectl
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
- 节点 `NotReady`:检查 CNI 是否就绪(`kubectl -n kube-flannel get pods`)、确认已 `swapoff -a`,并查看 `journalctl -u kubelet -f`。
|
||||||
|
- 无法拉取镜像:检查网络/镜像源,可用 `k8s-image-pull-and-import.sh` 预拉取。
|
||||||
|
- `kubectl` 连接异常:确认 `$HOME/.kube/config` 配置与权限。
|
||||||
|
|
||||||
|
### Istio 服务网格配置
|
||||||
|
|
||||||
|
本目录的 Kubernetes 集群安装完成后,如需使用 Istio 作为服务网格和入口网关,请参考:
|
||||||
|
|
||||||
|
#### Istio hostNetwork 模式配置
|
||||||
|
|
||||||
|
**适用场景**:
|
||||||
|
- 只有 master 节点有公网 IP
|
||||||
|
- 需要 Istio IngressGateway 替代 nginx-ingress-controller
|
||||||
|
- 需要 Istio 直接监听宿主机的 80/443 端口
|
||||||
|
|
||||||
|
**详细指南**:请参阅 [`istio-hostnetwork-notes.md`](./istio-hostnetwork-notes.md)
|
||||||
|
|
||||||
|
**快速概览**:
|
||||||
|
1. 安装 Istio(使用 `istioctl install` 或 Helm)
|
||||||
|
2. 按照指南将 `istio-ingressgateway` 切换为 hostNetwork 模式
|
||||||
|
3. 配置 Gateway 和 VirtualService 进行流量路由
|
||||||
|
4. 配置 TLS 证书 Secret
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
- 迁移前确保停止 nginx 或其他占用 80/443 的服务
|
||||||
|
- 需要将 TLS 证书 Secret 复制到 `istio-system` 命名空间
|
||||||
|
- hostNetwork 模式下,Service 类型可以是 `ClusterIP` 或 `LoadBalancer`
|
||||||
|
|
||||||
|
#### 其他 Istio 文档
|
||||||
|
|
||||||
|
- Istio 官方文档:https://istio.io/latest/docs/
|
||||||
|
- Istio 安装指南:https://istio.io/latest/docs/setup/install/
|
||||||
|
|
||||||
|
|
||||||
98
docs/kubernetes/install-kubectl-nodes.sh
Normal file
98
docs/kubernetes/install-kubectl-nodes.sh
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 为 node1 和 node2 安装 kubectl 脚本
|
||||||
|
# 功能: 从 master 传输 kubectl 二进制文件到其他节点
|
||||||
|
|
||||||
|
echo "==== 为 node1 和 node2 安装 kubectl ===="
|
||||||
|
|
||||||
|
# 定义节点列表
|
||||||
|
NODES=("172.17.0.15:master" "172.17.0.43:node1" "172.17.0.34:node2")
|
||||||
|
|
||||||
|
# 本机 IP 与 SSH 选项
|
||||||
|
LOCAL_IP=$(ip route get 1 | awk '{print $7; exit}')
|
||||||
|
SSH_OPTS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
|
||||||
|
# SSH 私钥(可用环境变量 SSH_KEY 覆盖),存在则自动携带
|
||||||
|
SSH_KEY_PATH=${SSH_KEY:-$HOME/.ssh/id_rsa}
|
||||||
|
[ -f "$SSH_KEY_PATH" ] && SSH_ID="-i $SSH_KEY_PATH" || SSH_ID=""
|
||||||
|
|
||||||
|
# 函数:在指定节点执行命令
|
||||||
|
execute_on_node() {
|
||||||
|
local ip="$1"
|
||||||
|
local hostname="$2"
|
||||||
|
local command="$3"
|
||||||
|
local description="$4"
|
||||||
|
|
||||||
|
echo "==== $description on $hostname ($ip) ===="
|
||||||
|
if [ "$ip" = "$LOCAL_IP" ] || [ "$hostname" = "master" ]; then
|
||||||
|
bash -lc "$command"
|
||||||
|
else
|
||||||
|
ssh $SSH_OPTS $SSH_ID ubuntu@$ip "$command"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 函数:传输文件到指定节点
|
||||||
|
copy_to_node() {
|
||||||
|
local ip="$1"
|
||||||
|
local hostname="$2"
|
||||||
|
local file="$3"
|
||||||
|
echo "传输 $file 到 $hostname ($ip)"
|
||||||
|
if [ "$ip" = "$LOCAL_IP" ] || [ "$hostname" = "master" ]; then
|
||||||
|
cp -f "$file" ~/
|
||||||
|
else
|
||||||
|
scp $SSH_OPTS $SSH_ID "$file" ubuntu@$ip:~/
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建 kubectl 安装脚本
|
||||||
|
cat > kubectl-install.sh << 'EOF_INSTALL'
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "==== 安装 kubectl ===="
|
||||||
|
|
||||||
|
# 1. 检查是否已安装
|
||||||
|
if command -v kubectl &> /dev/null; then
|
||||||
|
echo "kubectl 已安装,版本: $(kubectl version --client 2>/dev/null | grep 'Client Version' || echo 'unknown')"
|
||||||
|
echo "跳过安装"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. 安装 kubectl
|
||||||
|
echo "安装 kubectl..."
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y apt-transport-https ca-certificates curl
|
||||||
|
|
||||||
|
# 添加 Kubernetes 官方 GPG key
|
||||||
|
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
||||||
|
|
||||||
|
# 添加 Kubernetes apt 仓库
|
||||||
|
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
|
||||||
|
|
||||||
|
# 更新包列表并安装 kubectl
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y kubectl
|
||||||
|
|
||||||
|
# 3. 验证安装
|
||||||
|
echo "验证 kubectl 安装..."
|
||||||
|
kubectl version --client
|
||||||
|
|
||||||
|
echo "==== kubectl 安装完成 ===="
|
||||||
|
EOF_INSTALL
|
||||||
|
|
||||||
|
chmod +x kubectl-install.sh
|
||||||
|
|
||||||
|
# 为 node1 和 node2 安装 kubectl
|
||||||
|
for node in "${NODES[@]}"; do
|
||||||
|
IFS=':' read -r ip hostname <<< "$node"
|
||||||
|
if [ "$hostname" != "master" ]; then
|
||||||
|
copy_to_node "$ip" "$hostname" "kubectl-install.sh"
|
||||||
|
execute_on_node "$ip" "$hostname" "./kubectl-install.sh" "安装 kubectl"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
rm -f kubectl-install.sh
|
||||||
|
|
||||||
|
echo "==== 所有节点 kubectl 安装完成 ===="
|
||||||
454
docs/kubernetes/istio-hostnetwork-notes.md
Normal file
454
docs/kubernetes/istio-hostnetwork-notes.md
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
# Istio IngressGateway 切换为 hostNetwork 模式指南
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本指南适用于以下场景:
|
||||||
|
- 只有 master 节点有公网 IP
|
||||||
|
- 需要 Istio IngressGateway 替代 nginx-ingress-controller
|
||||||
|
- 需要 Istio 直接监听宿主机的 80/443 端口
|
||||||
|
|
||||||
|
### 为什么选择 hostNetwork?
|
||||||
|
|
||||||
|
1. **公网 IP 限制**:只有 master 节点有公网 IP,流量入口必须在 master
|
||||||
|
2. **端口一致性**:需要监听标准端口 80/443,与 nginx 保持一致
|
||||||
|
3. **无缝迁移**:无需修改 DNS 或负载均衡器配置
|
||||||
|
|
||||||
|
## 安装 Istio 1.27.1
|
||||||
|
|
||||||
|
### 1. 下载 istioctl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 下载 Istio 1.27.1
|
||||||
|
# 根据系统架构选择:x86_64 或 arm64
|
||||||
|
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.27.1 TARGET_ARCH=x86_64 sh -
|
||||||
|
|
||||||
|
# 进入目录
|
||||||
|
cd istio-1.27.1
|
||||||
|
|
||||||
|
# 临时添加到 PATH(当前会话有效)
|
||||||
|
export PATH=$PWD/bin:$PATH
|
||||||
|
|
||||||
|
# 或永久安装到系统路径
|
||||||
|
sudo cp bin/istioctl /usr/local/bin/
|
||||||
|
sudo chmod +x /usr/local/bin/istioctl
|
||||||
|
|
||||||
|
# 验证安装
|
||||||
|
istioctl version
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**:
|
||||||
|
- `TARGET_ARCH` 根据系统架构选择:`x86_64`(Intel/AMD)或 `arm64`(ARM)
|
||||||
|
- 如果使用临时 PATH,每次新终端会话都需要重新设置
|
||||||
|
- 推荐将 `istioctl` 复制到 `/usr/local/bin` 以便全局使用
|
||||||
|
|
||||||
|
### 2. 安装 Istio
|
||||||
|
|
||||||
|
使用 `default` profile 安装 Istio:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 Istio(使用 default profile)
|
||||||
|
istioctl install --set profile=default -y
|
||||||
|
|
||||||
|
# 验证安装
|
||||||
|
kubectl get pods -n istio-system
|
||||||
|
kubectl get svc -n istio-system
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期输出**:
|
||||||
|
- `istiod` Pod 应该处于 `Running` 状态
|
||||||
|
- `istio-ingressgateway` Pod 应该处于 `Running` 状态
|
||||||
|
- `istio-egressgateway` Pod 应该处于 `Running` 状态(可选)
|
||||||
|
|
||||||
|
### 3. 验证安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 Istio 组件状态
|
||||||
|
istioctl verify-install
|
||||||
|
|
||||||
|
# 查看 Istio 版本
|
||||||
|
istioctl version
|
||||||
|
|
||||||
|
# 检查所有命名空间的 Istio 资源
|
||||||
|
kubectl get crd | grep istio
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 4. 卸载 Istio(如需要)
|
||||||
|
|
||||||
|
如果需要卸载 Istio:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 卸载 Istio
|
||||||
|
istioctl uninstall --purge -y
|
||||||
|
|
||||||
|
# 删除命名空间
|
||||||
|
kubectl delete namespace istio-system
|
||||||
|
|
||||||
|
# 删除 CRD(可选,会删除所有 Istio 配置)
|
||||||
|
kubectl get crd | grep istio | awk '{print $1}' | xargs kubectl delete crd
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前置检查
|
||||||
|
|
||||||
|
**注意**:如果尚未安装 Istio,请先完成上述"安装 Istio 1.27.1"章节的步骤。
|
||||||
|
|
||||||
|
### 1. 确认集群状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查节点
|
||||||
|
kubectl get nodes
|
||||||
|
|
||||||
|
# 检查 Istio 组件(如果已安装)
|
||||||
|
kubectl get pods -n istio-system
|
||||||
|
|
||||||
|
# 检查当前 Service 配置(如果已安装)
|
||||||
|
kubectl get svc istio-ingressgateway -n istio-system
|
||||||
|
|
||||||
|
# 检查 Deployment 配置(如果已安装)
|
||||||
|
kubectl get deploy istio-ingressgateway -n istio-system -o yaml | head -n 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 释放端口(避免冲突)
|
||||||
|
|
||||||
|
**k3s 环境**:
|
||||||
|
- 如有 traefik,需要停止或释放 80/443
|
||||||
|
- 检查是否有其他服务占用端口:`ss -tlnp | grep -E ':(80|443) '`
|
||||||
|
|
||||||
|
**标准 Kubernetes 环境**:
|
||||||
|
```bash
|
||||||
|
# 停止 nginx-ingress-controller(如果存在)
|
||||||
|
kubectl scale deployment my-release-nginx-ingress-controller \
|
||||||
|
-n nginx-ingress-controller --replicas=0
|
||||||
|
|
||||||
|
# 验证端口已释放
|
||||||
|
ss -tlnp | grep -E ':(80|443) ' || echo "80/443 not listening"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整操作步骤
|
||||||
|
|
||||||
|
### 步骤 1:调整 Service(可选)
|
||||||
|
|
||||||
|
如果后续需要接真实 LB,可保留 `LoadBalancer` 类型;为便于本地测试,可先改为 `ClusterIP`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 修改 Service 类型为 ClusterIP
|
||||||
|
kubectl patch svc istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"replace","path":"/spec/type","value":"ClusterIP"}]'
|
||||||
|
|
||||||
|
# 调整端口映射(直通 80/443/15021)
|
||||||
|
kubectl patch svc istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"replace","path":"/spec/ports","value":[
|
||||||
|
{"name":"http","port":80,"targetPort":80,"protocol":"TCP"},
|
||||||
|
{"name":"https","port":443,"targetPort":443,"protocol":"TCP"},
|
||||||
|
{"name":"status-port","port":15021,"targetPort":15021,"protocol":"TCP"}]}]'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 2:启用 hostNetwork 模式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 启用 hostNetwork
|
||||||
|
kubectl patch deployment istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"add","path":"/spec/template/spec/hostNetwork","value":true}]'
|
||||||
|
|
||||||
|
# 2. 设置 DNS 策略
|
||||||
|
kubectl patch deployment istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"add","path":"/spec/template/spec/dnsPolicy","value":"ClusterFirstWithHostNet"}]'
|
||||||
|
|
||||||
|
# 3. 绑定到 master 节点(根据实际节点名调整)
|
||||||
|
kubectl patch deployment istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"add","path":"/spec/template/spec/nodeSelector","value":{"kubernetes.io/hostname":"master"}}]'
|
||||||
|
|
||||||
|
# 4. 添加容忍(如果 master 节点有 control-plane taint)
|
||||||
|
kubectl patch deployment istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"add","path":"/spec/template/spec/tolerations","value":[{"key":"node-role.kubernetes.io/control-plane","operator":"Exists","effect":"NoSchedule"}]}]'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 3:配置容器端口
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 让容器直接监听宿主机的 80/443/15021
|
||||||
|
kubectl patch deployment istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"replace","path":"/spec/template/spec/containers/0/ports","value":[
|
||||||
|
{"containerPort":80,"hostPort":80,"protocol":"TCP","name":"http"},
|
||||||
|
{"containerPort":443,"hostPort":443,"protocol":"TCP","name":"https"},
|
||||||
|
{"containerPort":15021,"hostPort":15021,"protocol":"TCP","name":"status-port"},
|
||||||
|
{"containerPort":15090,"protocol":"TCP","name":"http-envoy-prom"}]}]'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 4:配置安全上下文(解决权限问题)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 添加 NET_BIND_SERVICE 能力
|
||||||
|
kubectl patch deployment istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"add","path":"/spec/template/spec/containers/0/securityContext/capabilities/add","value":["NET_BIND_SERVICE"]}]'
|
||||||
|
|
||||||
|
# 2. 以 root 身份运行(允许绑定特权端口)
|
||||||
|
kubectl patch deployment istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"replace","path":"/spec/template/spec/securityContext/runAsNonRoot","value":false},\
|
||||||
|
{"op":"replace","path":"/spec/template/spec/securityContext/runAsUser","value":0},\
|
||||||
|
{"op":"replace","path":"/spec/template/spec/securityContext/runAsGroup","value":0}]'
|
||||||
|
|
||||||
|
# 3. 设置环境变量(告知 Istio 这是特权 Pod)
|
||||||
|
kubectl set env deployment/istio-ingressgateway -n istio-system ISTIO_META_UNPRIVILEGED_POD=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 5:重启 Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 先缩容到 0,避免 hostPort 冲突
|
||||||
|
kubectl scale deployment istio-ingressgateway -n istio-system --replicas=0
|
||||||
|
|
||||||
|
# 等待 Pod 完全终止
|
||||||
|
kubectl rollout status deployment/istio-ingressgateway -n istio-system --timeout=60s || true
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# 扩容到 1
|
||||||
|
kubectl scale deployment istio-ingressgateway -n istio-system --replicas=1
|
||||||
|
|
||||||
|
# 等待新 Pod 就绪
|
||||||
|
kubectl rollout status deployment/istio-ingressgateway -n istio-system --timeout=120s
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证配置
|
||||||
|
|
||||||
|
### 1. 检查 Pod 状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看 Pod 状态和 IP(hostNetwork 模式下 IP 应为节点 IP)
|
||||||
|
kubectl get pods -n istio-system -o wide
|
||||||
|
|
||||||
|
# 确认 hostNetwork 已启用
|
||||||
|
kubectl get pod -n istio-system -l app=istio-ingressgateway \
|
||||||
|
-o jsonpath='{.items[0].spec.hostNetwork}'
|
||||||
|
# 应该输出: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 检查端口监听
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在 master 节点上检查端口监听
|
||||||
|
ss -tlnp | grep -E ':(80|443|15021) '
|
||||||
|
|
||||||
|
# 或在 Pod 内部检查
|
||||||
|
kubectl exec -n istio-system deploy/istio-ingressgateway -- \
|
||||||
|
ss -tlnp | grep -E ':(80|443|15021) '
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 检查 Istio 配置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看 Envoy listener 配置
|
||||||
|
istioctl proxy-config listener deploy/istio-ingressgateway.istio-system
|
||||||
|
|
||||||
|
# 检查配置分析
|
||||||
|
istioctl analyze -A
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置 Gateway 和 VirtualService
|
||||||
|
|
||||||
|
### 1. 准备 TLS 证书 Secret
|
||||||
|
|
||||||
|
如果证书 Secret 在其他命名空间,需要复制到 `istio-system`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 复制 Secret(示例)
|
||||||
|
kubectl get secret <your-tls-secret> -n <source-namespace> -o yaml | \
|
||||||
|
sed "s/namespace: <source-namespace>/namespace: istio-system/" | \
|
||||||
|
kubectl apply -f -
|
||||||
|
|
||||||
|
# 验证
|
||||||
|
kubectl get secret <your-tls-secret> -n istio-system
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**:证书文件(`.crt`)如果包含多个 `BEGIN CERTIFICATE` 块是正常的,这是证书链(服务器证书 + 中间证书)。Kubernetes Secret 和 Istio Gateway 都支持这种格式。
|
||||||
|
|
||||||
|
### 2. 创建 Gateway
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: networking.istio.io/v1beta1
|
||||||
|
kind: Gateway
|
||||||
|
metadata:
|
||||||
|
name: devstar-gateway
|
||||||
|
namespace: istio-system
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
istio: ingressgateway
|
||||||
|
servers:
|
||||||
|
- port:
|
||||||
|
number: 80
|
||||||
|
name: http
|
||||||
|
protocol: HTTP
|
||||||
|
hosts:
|
||||||
|
- devstar.cn
|
||||||
|
- www.devstar.cn
|
||||||
|
- port:
|
||||||
|
number: 443
|
||||||
|
name: https
|
||||||
|
protocol: HTTPS
|
||||||
|
tls:
|
||||||
|
mode: SIMPLE
|
||||||
|
credentialName: devstar-studio-tls-secret-devstar-cn
|
||||||
|
hosts:
|
||||||
|
- devstar.cn
|
||||||
|
- www.devstar.cn
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 创建 VirtualService
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: networking.istio.io/v1beta1
|
||||||
|
kind: VirtualService
|
||||||
|
metadata:
|
||||||
|
name: devstar-studio-gitea
|
||||||
|
namespace: devstar-studio-ns
|
||||||
|
spec:
|
||||||
|
hosts:
|
||||||
|
- devstar.cn
|
||||||
|
- www.devstar.cn
|
||||||
|
gateways:
|
||||||
|
- istio-system/devstar-gateway
|
||||||
|
http:
|
||||||
|
# www.devstar.cn 重定向到 devstar.cn (308 永久重定向)
|
||||||
|
- match:
|
||||||
|
- headers:
|
||||||
|
host:
|
||||||
|
exact: www.devstar.cn
|
||||||
|
redirect:
|
||||||
|
authority: devstar.cn
|
||||||
|
redirectCode: 308
|
||||||
|
# devstar.cn 路由到后端服务
|
||||||
|
- match:
|
||||||
|
- uri:
|
||||||
|
prefix: /
|
||||||
|
route:
|
||||||
|
- destination:
|
||||||
|
host: devstar-studio-gitea-http
|
||||||
|
port:
|
||||||
|
number: 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 验证 Gateway 和 VirtualService
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 Gateway
|
||||||
|
kubectl get gateway -n istio-system
|
||||||
|
|
||||||
|
# 检查 VirtualService
|
||||||
|
kubectl get virtualservice -A
|
||||||
|
|
||||||
|
# 查看详细配置
|
||||||
|
kubectl describe gateway devstar-gateway -n istio-system
|
||||||
|
kubectl describe virtualservice devstar-studio-gitea -n devstar-studio-ns
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试访问
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# HTTP 测试
|
||||||
|
curl -H "Host: devstar.cn" http://<master-ip> -I
|
||||||
|
|
||||||
|
# HTTPS 测试
|
||||||
|
curl -k --resolve devstar.cn:443:<master-ip> https://devstar.cn -I
|
||||||
|
|
||||||
|
# 测试重定向(www.devstar.cn -> devstar.cn)
|
||||||
|
curl -I -H "Host: www.devstar.cn" http://<master-ip>
|
||||||
|
# 应该返回: HTTP/1.1 308 Permanent Redirect
|
||||||
|
```
|
||||||
|
|
||||||
|
## 启用服务网格(可选)
|
||||||
|
|
||||||
|
如果需要为其他命名空间启用自动 sidecar 注入:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 为命名空间启用自动注入
|
||||||
|
kubectl label namespace <namespace> istio-injection=enabled
|
||||||
|
|
||||||
|
# 验证
|
||||||
|
kubectl get namespace -L istio-injection
|
||||||
|
|
||||||
|
# 重启现有 Pod 以注入 sidecar
|
||||||
|
kubectl rollout restart deployment -n <namespace>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. Pod 一直 Pending
|
||||||
|
|
||||||
|
**原因**:旧 Pod 仍占用 hostPort,新 Pod 无法调度。
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
```bash
|
||||||
|
# 手动删除旧 Pod
|
||||||
|
kubectl delete pod -n istio-system -l app=istio-ingressgateway
|
||||||
|
|
||||||
|
# 或先缩容再扩容
|
||||||
|
kubectl scale deployment istio-ingressgateway -n istio-system --replicas=0
|
||||||
|
kubectl scale deployment istio-ingressgateway -n istio-system --replicas=1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Envoy 报 "Permission denied" 无法绑定 80/443
|
||||||
|
|
||||||
|
**原因**:容器没有足够权限绑定特权端口。
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 确认已添加 `NET_BIND_SERVICE` capability
|
||||||
|
- 确认 `runAsUser: 0` 和 `runAsNonRoot: false`
|
||||||
|
- 确认 `ISTIO_META_UNPRIVILEGED_POD=false`
|
||||||
|
|
||||||
|
### 3. Istiod 日志显示 "skipping privileged gateway port"
|
||||||
|
|
||||||
|
**原因**:Istio 认为 Pod 是无特权模式。
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
```bash
|
||||||
|
kubectl set env deployment/istio-ingressgateway -n istio-system ISTIO_META_UNPRIVILEGED_POD=false
|
||||||
|
kubectl rollout restart deployment istio-ingressgateway -n istio-system
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Gateway 冲突(IST0145)
|
||||||
|
|
||||||
|
**原因**:多个 Gateway 使用相同的 selector 和端口,但 hosts 冲突。
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 合并多个 Gateway 到一个,在 `hosts` 中列出所有域名
|
||||||
|
- 或确保不同 Gateway 的 `hosts` 不重叠
|
||||||
|
|
||||||
|
## 回滚方案
|
||||||
|
|
||||||
|
如果需要回滚到默认配置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 恢复 nginx(如果之前使用)
|
||||||
|
kubectl scale deployment my-release-nginx-ingress-controller \
|
||||||
|
-n nginx-ingress-controller --replicas=1
|
||||||
|
|
||||||
|
# 2. 恢复 Istio 为默认配置
|
||||||
|
istioctl install --set profile=default -y
|
||||||
|
|
||||||
|
# 3. 或手动删除 hostNetwork 相关配置
|
||||||
|
kubectl patch deployment istio-ingressgateway -n istio-system --type='json' \
|
||||||
|
-p='[{"op":"remove","path":"/spec/template/spec/hostNetwork"}]'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 端口映射说明
|
||||||
|
|
||||||
|
### Istio 默认端口配置
|
||||||
|
|
||||||
|
- **容器内部端口**:Istio 默认让 Envoy 监听 8080(HTTP)和 8443(HTTPS)
|
||||||
|
- **Service 端口映射**:Service 的 80 端口映射到容器的 8080(targetPort: 8080),443 映射到 8443
|
||||||
|
- **为什么不是 80/443**:这是 Istio 的设计,避免与主机上的其他服务冲突
|
||||||
|
|
||||||
|
### hostNetwork 模式下的端口配置
|
||||||
|
|
||||||
|
使用 hostNetwork 模式时:
|
||||||
|
- 容器直接使用主机网络,需要监听主机的 80/443 端口
|
||||||
|
- 因此需要修改容器端口配置,让容器监听 80/443 而不是 8080/8443
|
||||||
|
- 同时需要配置 IstioOperator 的 values,让 Envoy 实际监听 80/443
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **端口冲突**:迁移前确保停止 nginx 或其他占用 80/443 的服务
|
||||||
|
2. **Sidecar 资源**:每个 Pod 会增加 ~100MB 内存和 ~100m CPU
|
||||||
|
3. **TLS 证书**:需要将证书 Secret 复制到 istio-system 命名空间,或通过 Gateway 配置指定命名空间
|
||||||
|
4. **性能影响**:sidecar 会增加少量延迟(通常 <1ms)
|
||||||
|
5. **Service 类型**:hostNetwork 模式下,Service 类型可以是 `ClusterIP` 或 `LoadBalancer`,不影响功能
|
||||||
122
docs/kubernetes/k8s-image-pull-and-import.sh
Normal file
122
docs/kubernetes/k8s-image-pull-and-import.sh
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 说明:
|
||||||
|
# 在 master、node1、node2 三台节点上分别拉取指定镜像, 并导入到 containerd (k8s.io 命名空间)
|
||||||
|
# 不通过主机分发镜像归档, 而是每台节点各自拉取/导入。
|
||||||
|
#
|
||||||
|
# 使用示例:
|
||||||
|
# chmod +x k8s-image-pull-and-import.sh
|
||||||
|
# ./k8s-image-pull-and-import.sh beppeb/devstar-controller-manager:3.0.0.without_istio
|
||||||
|
#
|
||||||
|
# 可选环境变量:
|
||||||
|
# SSH_KEY 指定私钥路径 (默认: ~/.ssh/id_rsa, 若存在自动携带)
|
||||||
|
|
||||||
|
echo "==== K8s 镜像拉取并导入 containerd ===="
|
||||||
|
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
echo "用法: $0 <IMAGE[:TAG]>"
|
||||||
|
echo "示例: $0 beppeb/devstar-controller-manager:3.0.0.without_istio"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
IMAGE_INPUT="$1"
|
||||||
|
|
||||||
|
# 规范化镜像名, 若无 registry 前缀则补全 docker.io/
|
||||||
|
normalize_image() {
|
||||||
|
local img="$1"
|
||||||
|
if [[ "$img" != */*/* ]]; then
|
||||||
|
# 只有一个斜杠(如 library/nginx 或 beppeb/devstar-...): 仍可能缺少 registry
|
||||||
|
# Docker 的默认 registry 是 docker.io
|
||||||
|
echo "docker.io/${img}"
|
||||||
|
else
|
||||||
|
echo "$img"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
CANONICAL_IMAGE=$(normalize_image "$IMAGE_INPUT")
|
||||||
|
echo "目标镜像: ${CANONICAL_IMAGE}"
|
||||||
|
|
||||||
|
# 节点列表: 与 k8s-step1-prepare-env.sh 风格一致
|
||||||
|
NODES=("172.17.0.15:master" "172.17.0.43:node1" "172.17.0.34:node2")
|
||||||
|
|
||||||
|
# 本机 IP 与 SSH 选项
|
||||||
|
LOCAL_IP=$(ip route get 1 | awk '{print $7; exit}')
|
||||||
|
SSH_OPTS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
|
||||||
|
SSH_KEY_PATH=${SSH_KEY:-$HOME/.ssh/id_rsa}
|
||||||
|
[ -f "$SSH_KEY_PATH" ] && SSH_ID="-i $SSH_KEY_PATH" || SSH_ID=""
|
||||||
|
|
||||||
|
run_remote() {
|
||||||
|
local ip="$1"; shift
|
||||||
|
local cmd="$*"
|
||||||
|
if [ "$ip" = "$LOCAL_IP" ]; then
|
||||||
|
bash -lc "$cmd"
|
||||||
|
else
|
||||||
|
ssh $SSH_OPTS $SSH_ID ubuntu@"$ip" "$cmd"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 在远端节点执行: 使用 docker 或 containerd 拉取镜像, 并确保导入到 containerd k8s.io
|
||||||
|
remote_pull_and_import_cmd() {
|
||||||
|
local image="$1"
|
||||||
|
# 注意: 使用单引号包裹, 传到远端后再展开变量
|
||||||
|
cat <<'EOF_REMOTE'
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IMAGE_REMOTE="$IMAGE_PLACEHOLDER"
|
||||||
|
|
||||||
|
has_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
echo "[\"$(hostname)\"] 处理镜像: ${IMAGE_REMOTE}"
|
||||||
|
|
||||||
|
# 优先尝试 docker 拉取, 成功后直接导入 containerd (无需落盘)
|
||||||
|
if has_cmd docker; then
|
||||||
|
echo "[\"$(hostname)\"] 使用 docker pull"
|
||||||
|
sudo docker pull "${IMAGE_REMOTE}"
|
||||||
|
echo "[\"$(hostname)\"] 导入到 containerd (k8s.io)"
|
||||||
|
sudo docker save "${IMAGE_REMOTE}" | sudo ctr -n k8s.io images import - >/dev/null
|
||||||
|
else
|
||||||
|
echo "[\"$(hostname)\"] 未检测到 docker, 尝试使用 containerd 拉取"
|
||||||
|
# containerd 直接拉取到 k8s.io 命名空间
|
||||||
|
sudo ctr -n k8s.io images pull --all-platforms "${IMAGE_REMOTE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 规范化 tag: 若镜像缺少 docker.io 前缀, 在 containerd 内补齐一份别名
|
||||||
|
NEED_PREFIX=0
|
||||||
|
if [[ "${IMAGE_REMOTE}" != docker.io/* ]]; then
|
||||||
|
NEED_PREFIX=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NEED_PREFIX" -eq 1 ]; then
|
||||||
|
# 仅当不存在 docker.io/ 前缀时, 补一个 docker.io/ 的 tag, 方便与清单匹配
|
||||||
|
# 计算补齐后的名字
|
||||||
|
if [[ "${IMAGE_REMOTE}" == */*/* ]]; then
|
||||||
|
# 已有显式 registry, 不重复打 tag
|
||||||
|
:
|
||||||
|
else
|
||||||
|
FIXED="docker.io/${IMAGE_REMOTE}"
|
||||||
|
echo "[\"$(hostname)\"] 为 containerd 打标签: ${FIXED}"
|
||||||
|
sudo ctr -n k8s.io images tag "${IMAGE_REMOTE}" "${FIXED}" || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[\"$(hostname)\"] 验证镜像是否存在于 containerd:"
|
||||||
|
sudo ctr -n k8s.io images ls | grep -E "$(printf '%s' "${IMAGE_REMOTE}" | sed 's/[\/.\-]/\\&/g')" || true
|
||||||
|
EOF_REMOTE
|
||||||
|
}
|
||||||
|
|
||||||
|
# 遍历节点执行
|
||||||
|
for node in "${NODES[@]}"; do
|
||||||
|
IFS=':' read -r ip hostname <<< "$node"
|
||||||
|
echo "==== 在 ${hostname} (${ip}) 执行镜像拉取与导入 ===="
|
||||||
|
# 将占位符替换为实际镜像并远程执行
|
||||||
|
remote_script=$(remote_pull_and_import_cmd "$CANONICAL_IMAGE")
|
||||||
|
# 安全替换占位符为镜像名
|
||||||
|
remote_script=${remote_script//\$IMAGE_PLACEHOLDER/$CANONICAL_IMAGE}
|
||||||
|
run_remote "$ip" "$remote_script"
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "==== 完成 ===="
|
||||||
|
|
||||||
|
|
||||||
69
docs/kubernetes/k8s-install-all.sh
Normal file
69
docs/kubernetes/k8s-install-all.sh
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Kubernetes 集群一键安装脚本
|
||||||
|
# 功能: 按顺序执行所有安装步骤
|
||||||
|
|
||||||
|
echo "==== Kubernetes 集群一键安装 ===="
|
||||||
|
echo "集群信息:"
|
||||||
|
echo "- Master: 172.17.0.15"
|
||||||
|
echo "- Node1: 172.17.0.43"
|
||||||
|
echo "- Node2: 172.17.0.34"
|
||||||
|
echo "- Kubernetes 版本: v1.32.3"
|
||||||
|
echo "- 网络插件: Flannel"
|
||||||
|
echo "- 容器运行时: containerd"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 检查脚本文件是否存在
|
||||||
|
SCRIPTS=(
|
||||||
|
"k8s-step1-prepare-env.sh"
|
||||||
|
"k8s-step2-install-containerd.sh"
|
||||||
|
"k8s-step3-install-components.sh"
|
||||||
|
"k8s-step4-init-cluster.sh"
|
||||||
|
"k8s-step5-install-flannel.sh"
|
||||||
|
"k8s-step6-join-nodes.sh"
|
||||||
|
)
|
||||||
|
|
||||||
|
for script in "${SCRIPTS[@]}"; do
|
||||||
|
if [ ! -f "$script" ]; then
|
||||||
|
echo "错误: 找不到脚本文件 $script"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "所有脚本文件检查完成,开始安装..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 执行安装步骤
|
||||||
|
echo "==== 步骤 1: 环境准备 ===="
|
||||||
|
./k8s-step1-prepare-env.sh
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "==== 步骤 2: 安装 containerd ===="
|
||||||
|
./k8s-step2-install-containerd.sh
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "==== 步骤 3: 安装 Kubernetes 组件 ===="
|
||||||
|
./k8s-step3-install-components.sh
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "==== 步骤 4: 初始化集群 ===="
|
||||||
|
./k8s-step4-init-cluster.sh
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "==== 步骤 5: 安装 Flannel 网络插件 ===="
|
||||||
|
./k8s-step5-install-flannel.sh
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "==== 步骤 6: 节点加入集群 ===="
|
||||||
|
./k8s-step6-join-nodes.sh
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "==== 安装完成 ===="
|
||||||
|
echo "集群状态:"
|
||||||
|
kubectl get nodes
|
||||||
|
echo ""
|
||||||
|
kubectl get pods -A
|
||||||
|
echo ""
|
||||||
|
echo "集群已就绪,可以开始部署应用!"
|
||||||
|
|
||||||
524
docs/kubernetes/k8s-installtion.md
Normal file
524
docs/kubernetes/k8s-installtion.md
Normal file
@@ -0,0 +1,524 @@
|
|||||||
|
# Kubernetes 集群安装文档
|
||||||
|
|
||||||
|
## 📋 集群信息
|
||||||
|
- **Master**: 172.17.0.15 (master)
|
||||||
|
- **Node1**: 172.17.0.43 (node1)
|
||||||
|
- **Node2**: 172.17.0.34 (node2)
|
||||||
|
- **Kubernetes 版本**: v1.32.3
|
||||||
|
- **容器运行时**: containerd
|
||||||
|
- **网络插件**: Flannel
|
||||||
|
- **镜像仓库**: 阿里云镜像
|
||||||
|
|
||||||
|
## 🎯 安装方式
|
||||||
|
**模块化安装**: 每个脚本功能清晰,可以单独执行或按顺序执行
|
||||||
|
|
||||||
|
## 📋 安装脚本
|
||||||
|
|
||||||
|
### 🔧 脚本列表
|
||||||
|
1. **`k8s-step1-prepare-env.sh`** - 环境准备 (所有节点)
|
||||||
|
2. **`k8s-step2-install-containerd.sh`** - 容器运行时安装 (所有节点)
|
||||||
|
3. **`k8s-step3-install-components.sh`** - Kubernetes 组件安装 (所有节点)
|
||||||
|
4. **`k8s-step4-init-cluster.sh`** - 集群初始化 (Master 节点)
|
||||||
|
5. **`k8s-step5-install-flannel.sh`** - 网络插件安装 (Master 节点)
|
||||||
|
6. **`k8s-step6-join-nodes.sh`** - 节点加入集群 (Node1, Node2)
|
||||||
|
7. **`k8s-install-all.sh`** - 主控制脚本 (按顺序执行所有步骤)
|
||||||
|
|
||||||
|
### 🌐 网络配置脚本
|
||||||
|
- **`setup-master-gateway.sh`** - Master 节点网关配置
|
||||||
|
- **`setup-node1.sh`** - Node1 网络路由配置
|
||||||
|
- **`setup-node2.sh`** - Node2 网络路由配置
|
||||||
|
|
||||||
|
### 🔧 辅助工具脚本
|
||||||
|
- **`install-kubectl-nodes.sh`** - 为其他节点安装 kubectl
|
||||||
|
|
||||||
|
### 🚀 使用方法
|
||||||
|
|
||||||
|
#### 方法 1: 一键安装
|
||||||
|
```bash
|
||||||
|
# 在 Master 节点运行
|
||||||
|
./k8s-install-all.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方法 2: 分步安装
|
||||||
|
```bash
|
||||||
|
# 按顺序执行每个步骤
|
||||||
|
./k8s-step1-prepare-env.sh
|
||||||
|
./k8s-step2-install-containerd.sh
|
||||||
|
./k8s-step3-install-components.sh
|
||||||
|
./k8s-step4-init-cluster.sh
|
||||||
|
./k8s-step5-install-flannel.sh
|
||||||
|
./k8s-step6-join-nodes.sh
|
||||||
|
|
||||||
|
# 可选:为其他节点安装 kubectl
|
||||||
|
./install-kubectl-nodes.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 安装步骤
|
||||||
|
|
||||||
|
### ✅ 步骤 1: 环境准备(已完成)
|
||||||
|
- [x] 云主机重装系统:确认系统盘数据清空,无残留 kube 目录与服务
|
||||||
|
- [x] 主机名设置:`master`、`node1`、`node2`(不在节点脚本中写入 hosts)
|
||||||
|
- [x] Master 配置 NAT 网关:开启 `net.ipv4.ip_forward`,设置 `iptables` MASQUERADE 并持久化
|
||||||
|
- [x] 基础内核与网络:开启 `overlay`、`br_netfilter`;`sysctl` 应用桥接与转发参数
|
||||||
|
- [x] 关闭 swap:禁用并注释 `/etc/fstab` 对应项
|
||||||
|
- [x] 防火墙:禁用 `ufw`,确保必要端口不被拦截
|
||||||
|
- [x] SSH 信任:在 master 生成密钥并分发到 `node1/node2`,验证免密可达
|
||||||
|
|
||||||
|
### ✅ 步骤 2: 容器运行时准备(所有节点,已完成)
|
||||||
|
- [x] 更新系统包,安装依赖工具:`curl`、`wget`、`gnupg`、`ca-certificates`、`apt-transport-https` 等
|
||||||
|
- [x] 安装 containerd 并生成默认配置 `/etc/containerd/config.toml`
|
||||||
|
- [x] 配置镜像加速:docker.io/quay.io 使用腾讯云镜像,其他使用高校镜像
|
||||||
|
- [x] 安装 CNI 插件 v1.3.0(在 master 预下载并分发至 node1/node2)
|
||||||
|
- [x] 启用并开机自启 `containerd`,确认服务状态正常
|
||||||
|
|
||||||
|
### ✅ 步骤 3: 安装 Kubernetes 组件(所有节点,已完成)
|
||||||
|
- [x] 添加 Kubernetes APT 仓库(pkgs.k8s.io v1.32),修复 GPG key 与源配置问题
|
||||||
|
- [x] 安装并锁定版本:`kubelet`、`kubeadm`、`kubectl` 为 `v1.32.3`
|
||||||
|
- [x] 配置 kubelet:使用 `systemd` cgroup,与 containerd 对齐,写入完整配置文件
|
||||||
|
- [x] 启用并启动 `kubelet` 服务
|
||||||
|
|
||||||
|
### ✅ 步骤 4: 集群初始化(Master 节点,已完成)
|
||||||
|
- [x] 执行 `kubeadm init` 完成初始化:包含 `controlPlaneEndpoint=172.17.0.15:6443`、Networking(ServiceCIDR `10.96.0.0/12`、PodCIDR `10.244.0.0/16`)、`imageRepository`(Aliyun)
|
||||||
|
- [x] 拷贝 `admin.conf` 到 `~/.kube/config` 并验证控制面组件:`etcd`、`kube-apiserver`、`kube-controller-manager`、`kube-scheduler`、`kube-proxy` 均 Running;`coredns` Pending(等待安装网络插件)
|
||||||
|
- [x] 生成并使用 `kubeadm token create --print-join-command` 生成 join 命令
|
||||||
|
|
||||||
|
### ✅ 步骤 5: 网络插件安装 (Master 节点,已完成)
|
||||||
|
- [x] 下载并应用 Flannel v0.27.4 清单
|
||||||
|
- [x] 匹配 Pod CIDR `10.244.0.0/16`,等待组件 Ready
|
||||||
|
- [x] 配置 Flannel 使用国内镜像源(registry-k8s-io.mirrors.sjtug.sjtu.edu.cn、ghcr.tencentcloudcr.com)
|
||||||
|
- [x] 预拉取所有 Flannel 镜像并打标签
|
||||||
|
- [x] 等待所有网络组件就绪:kube-flannel-ds、coredns
|
||||||
|
|
||||||
|
### ✅ 步骤 6: 节点加入集群(已完成)
|
||||||
|
- [x] 读取 `node-join-command.txt` 文件中的 join 命令
|
||||||
|
- [x] 在 `node1/node2` 执行 join,加入成功后验证 `Ready`
|
||||||
|
- [x] 验证所有节点状态:master (Ready, control-plane)、node1 (Ready)、node2 (Ready)
|
||||||
|
|
||||||
|
### ✅ 步骤 7: 集群验证(已完成)
|
||||||
|
- [x] `kubectl get nodes/pods -A` 基线检查
|
||||||
|
- [x] 所有 Pod 状态为 Running:控制面组件、网络组件、系统组件
|
||||||
|
- [x] 集群完全就绪,可以部署应用
|
||||||
|
|
||||||
|
### ✅ 步骤 8: 为其他节点安装 kubectl(已完成)
|
||||||
|
- [x] 在 node1 和 node2 上安装 kubectl v1.32.3
|
||||||
|
- [x] 复制 master 的 kubeconfig 配置文件到其他节点
|
||||||
|
- [x] 验证所有节点都能正常访问 Kubernetes 集群
|
||||||
|
|
||||||
|
## 📝 详细安装过程记录
|
||||||
|
|
||||||
|
### 步骤 1: 系统环境准备
|
||||||
|
|
||||||
|
#### 1.1 系统重装与清理
|
||||||
|
- 腾讯云服务器实例重装系统,确保硬盘完全清空
|
||||||
|
- 验证无残留 Kubernetes 相关目录和服务
|
||||||
|
|
||||||
|
#### 1.2 主机名配置
|
||||||
|
```bash
|
||||||
|
# Master 节点
|
||||||
|
sudo hostnamectl set-hostname master
|
||||||
|
|
||||||
|
# Node1 节点
|
||||||
|
sudo hostnamectl set-hostname node1
|
||||||
|
|
||||||
|
# Node2 节点
|
||||||
|
sudo hostnamectl set-hostname node2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.3 网络配置
|
||||||
|
|
||||||
|
> **提示**: 可以使用提供的脚本自动配置网络:
|
||||||
|
> - `./setup-master-gateway.sh` - 在 Master 节点执行
|
||||||
|
> - `./setup-node1.sh` - 在 Node1 节点执行
|
||||||
|
> - `./setup-node2.sh` - 在 Node2 节点执行
|
||||||
|
|
||||||
|
**Master 节点配置为 NAT 网关:**
|
||||||
|
```bash
|
||||||
|
# 启用 IP 转发
|
||||||
|
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
|
||||||
|
sudo sysctl -p
|
||||||
|
|
||||||
|
# 清空现有 iptables 规则
|
||||||
|
sudo iptables -F
|
||||||
|
sudo iptables -t nat -F
|
||||||
|
sudo iptables -t mangle -F
|
||||||
|
sudo iptables -X
|
||||||
|
sudo iptables -t nat -X
|
||||||
|
sudo iptables -t mangle -X
|
||||||
|
|
||||||
|
# 设置默认策略
|
||||||
|
sudo iptables -P INPUT ACCEPT
|
||||||
|
sudo iptables -P FORWARD ACCEPT
|
||||||
|
sudo iptables -P OUTPUT ACCEPT
|
||||||
|
|
||||||
|
# 配置 NAT 规则 - 允许内网节点通过 master 访问外网
|
||||||
|
sudo iptables -t nat -A POSTROUTING -s 172.17.0.0/20 -o eth0 -j MASQUERADE
|
||||||
|
|
||||||
|
# 允许转发来自内网的流量
|
||||||
|
sudo iptables -A FORWARD -s 172.17.0.0/20 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -d 172.17.0.0/20 -j ACCEPT
|
||||||
|
|
||||||
|
# 保存 iptables 规则
|
||||||
|
sudo apt update && sudo apt install -y iptables-persistent
|
||||||
|
sudo netfilter-persistent save
|
||||||
|
```
|
||||||
|
|
||||||
|
**Node1 和 Node2 配置路由:**
|
||||||
|
```bash
|
||||||
|
# 删除默认网关(如果存在)
|
||||||
|
sudo ip route del default 2>/dev/null || true
|
||||||
|
|
||||||
|
# 添加默认网关指向 master
|
||||||
|
sudo ip route add default via 172.17.0.15
|
||||||
|
|
||||||
|
# 验证网络连通性
|
||||||
|
ping -c 2 172.17.0.15 && echo "✓ 可以访问 master" || echo "✗ 无法访问 master"
|
||||||
|
ping -c 2 8.8.8.8 && echo "✓ 可以访问外网" || echo "✗ 无法访问外网"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.4 SSH 密钥配置
|
||||||
|
```bash
|
||||||
|
# Master 节点生成 SSH 密钥
|
||||||
|
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""
|
||||||
|
|
||||||
|
# 将公钥复制到 Node1 和 Node2
|
||||||
|
ssh-copy-id ubuntu@172.17.0.43
|
||||||
|
ssh-copy-id ubuntu@172.17.0.34
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 2: 基础环境准备(所有节点)
|
||||||
|
|
||||||
|
#### 2.1 系统更新
|
||||||
|
```bash
|
||||||
|
sudo apt update && sudo apt upgrade -y
|
||||||
|
sudo apt install -y curl wget vim net-tools gnupg lsb-release ca-certificates apt-transport-https
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 内核参数配置
|
||||||
|
```bash
|
||||||
|
# 加载内核模块
|
||||||
|
sudo modprobe overlay
|
||||||
|
sudo modprobe br_netfilter
|
||||||
|
|
||||||
|
# 配置内核参数
|
||||||
|
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
|
||||||
|
overlay
|
||||||
|
br_netfilter
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
|
||||||
|
net.bridge.bridge-nf-call-iptables = 1
|
||||||
|
net.bridge.bridge-nf-call-ip6tables = 1
|
||||||
|
net.ipv4.ip_forward = 1
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo sysctl --system
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.3 禁用 Swap
|
||||||
|
```bash
|
||||||
|
sudo swapoff -a
|
||||||
|
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.4 防火墙配置
|
||||||
|
```bash
|
||||||
|
sudo ufw disable
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 3: 容器运行时安装(所有节点)
|
||||||
|
|
||||||
|
#### 3.1 安装 containerd
|
||||||
|
```bash
|
||||||
|
# 安装 containerd
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y containerd
|
||||||
|
|
||||||
|
# ① 停止 containerd
|
||||||
|
sudo systemctl stop containerd
|
||||||
|
|
||||||
|
# ② 生成默认配置
|
||||||
|
sudo containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
|
||||||
|
|
||||||
|
# ③ 注入镜像加速配置(docker.io/quay.io:腾讯云,其它:高校镜像优先)
|
||||||
|
sudo sed -i '/\[plugins."io.containerd.grpc.v1.cri".registry.mirrors\]/a\
|
||||||
|
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]\n endpoint = ["https://mirror.ccs.tencentyun.com"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."quay.io"]\n endpoint = ["https://quay.tencentcloudcr.com"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."ghcr.io"]\n endpoint = ["https://ghcr.nju.edu.cn"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]\n endpoint = ["https://gcr.nju.edu.cn"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]\n endpoint = ["https://registry-k8s-io.mirrors.sjtug.sjtu.edu.cn"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."gcr.io"]\n endpoint = ["https://gcr.nju.edu.cn"]' /etc/containerd/config.toml
|
||||||
|
|
||||||
|
# ④ 重新加载并启动 containerd
|
||||||
|
sudo systemctl daemon-reexec
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl restart containerd
|
||||||
|
|
||||||
|
# ⑤ 检查服务状态
|
||||||
|
sudo systemctl status containerd --no-pager -l
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 安装 CNI 插件
|
||||||
|
```bash
|
||||||
|
# 下载 CNI 插件
|
||||||
|
CNI_VERSION="v1.3.0"
|
||||||
|
CNI_TGZ="cni-plugins-linux-amd64-${CNI_VERSION}.tgz"
|
||||||
|
|
||||||
|
# 下载 CNI 插件
|
||||||
|
curl -L --fail --retry 3 --connect-timeout 10 \
|
||||||
|
-o "$CNI_TGZ" \
|
||||||
|
"https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/$CNI_TGZ"
|
||||||
|
|
||||||
|
# 安装 CNI 插件
|
||||||
|
sudo mkdir -p /opt/cni/bin
|
||||||
|
sudo tar -xzf "$CNI_TGZ" -C /opt/cni/bin/
|
||||||
|
rm -f "$CNI_TGZ"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 4: Kubernetes 组件安装(所有节点)
|
||||||
|
|
||||||
|
#### 4.1 添加 Kubernetes 仓库
|
||||||
|
```bash
|
||||||
|
# 添加 Kubernetes 仓库 (pkgs.k8s.io v1.32)
|
||||||
|
# 确保 keyrings 目录存在并可读
|
||||||
|
sudo install -m 0755 -d /etc/apt/keyrings
|
||||||
|
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
||||||
|
sudo chmod a+r /etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list >/dev/null
|
||||||
|
|
||||||
|
# 更新包列表
|
||||||
|
sudo apt update
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 安装 Kubernetes 组件
|
||||||
|
```bash
|
||||||
|
# 安装 kubelet, kubeadm, kubectl
|
||||||
|
sudo apt install -y kubelet kubeadm kubectl
|
||||||
|
|
||||||
|
# 锁定版本防止自动更新
|
||||||
|
sudo apt-mark hold kubelet kubeadm kubectl
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.3 配置 kubelet
|
||||||
|
```bash
|
||||||
|
# 配置 kubelet
|
||||||
|
sudo mkdir -p /var/lib/kubelet
|
||||||
|
cat <<EOF | sudo tee /var/lib/kubelet/config.yaml
|
||||||
|
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||||
|
kind: KubeletConfiguration
|
||||||
|
authentication:
|
||||||
|
anonymous:
|
||||||
|
enabled: false
|
||||||
|
webhook:
|
||||||
|
enabled: true
|
||||||
|
x509:
|
||||||
|
clientCAFile: /etc/kubernetes/pki/ca.crt
|
||||||
|
authorization:
|
||||||
|
mode: Webhook
|
||||||
|
clusterDomain: cluster.local
|
||||||
|
clusterDNS:
|
||||||
|
- 10.96.0.10
|
||||||
|
containerRuntimeEndpoint: unix:///var/run/containerd/containerd.sock
|
||||||
|
cgroupDriver: systemd
|
||||||
|
failSwapOn: false
|
||||||
|
hairpinMode: promiscuous-bridge
|
||||||
|
healthzBindAddress: 127.0.0.1
|
||||||
|
healthzPort: 10248
|
||||||
|
httpCheckFrequency: 20s
|
||||||
|
imageMinimumGCAge: 2m0s
|
||||||
|
imageGCHighThresholdPercent: 85
|
||||||
|
imageGCLowThresholdPercent: 80
|
||||||
|
iptablesDropBit: 15
|
||||||
|
iptablesMasqueradeBit: 15
|
||||||
|
kubeAPIBurst: 10
|
||||||
|
kubeAPIQPS: 5
|
||||||
|
makeIPTablesUtilChains: true
|
||||||
|
maxOpenFiles: 1000000
|
||||||
|
maxPods: 110
|
||||||
|
nodeStatusUpdateFrequency: 10s
|
||||||
|
oomScoreAdj: -999
|
||||||
|
podCIDR: 10.244.0.0/16
|
||||||
|
registryBurst: 10
|
||||||
|
registryPullQPS: 5
|
||||||
|
resolvConf: /etc/resolv.conf
|
||||||
|
rotateCertificates: true
|
||||||
|
runtimeRequestTimeout: 2m0s
|
||||||
|
serializeImagePulls: true
|
||||||
|
serverTLSBootstrap: true
|
||||||
|
streamingConnectionIdleTimeout: 4h0m0s
|
||||||
|
syncFrequency: 1m0s
|
||||||
|
volumeStatsAggPeriod: 1m0s
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 启动 kubelet
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable kubelet
|
||||||
|
sudo systemctl start kubelet
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 5: 集群初始化(Master 节点)
|
||||||
|
|
||||||
|
#### 5.1 初始化集群
|
||||||
|
```bash
|
||||||
|
# 初始化 Kubernetes 集群
|
||||||
|
sudo kubeadm init \
|
||||||
|
--apiserver-advertise-address=172.17.0.15 \
|
||||||
|
--control-plane-endpoint=172.17.0.15:6443 \
|
||||||
|
--kubernetes-version=v1.32.3 \
|
||||||
|
--service-cidr=10.96.0.0/12 \
|
||||||
|
--pod-network-cidr=10.244.0.0/16 \
|
||||||
|
--image-repository=registry.aliyuncs.com/google_containers \
|
||||||
|
--upload-certs \
|
||||||
|
--ignore-preflight-errors=Swap
|
||||||
|
|
||||||
|
# 配置 kubectl
|
||||||
|
mkdir -p $HOME/.kube
|
||||||
|
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
|
||||||
|
sudo chown $(id -u):$(id -g) $HOME/.kube/config
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2 生成节点加入命令
|
||||||
|
```bash
|
||||||
|
# 生成节点加入命令
|
||||||
|
JOIN_COMMAND=$(kubeadm token create --print-join-command)
|
||||||
|
echo "节点加入命令:"
|
||||||
|
echo "$JOIN_COMMAND"
|
||||||
|
echo "$JOIN_COMMAND" > node-join-command.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 6: 网络插件安装(Master 节点)
|
||||||
|
|
||||||
|
#### 6.1 下载 Flannel 清单
|
||||||
|
```bash
|
||||||
|
# 下载 Flannel v0.27.4
|
||||||
|
FLANNEL_VER="v0.27.4"
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/flannel-io/flannel/${FLANNEL_VER}/Documentation/kube-flannel.yml -O
|
||||||
|
|
||||||
|
# 修改 Flannel 配置
|
||||||
|
sed -i 's|"Network": "10.244.0.0/16"|"Network": "10.244.0.0/16"|g' kube-flannel.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.2 预拉取 Flannel 镜像
|
||||||
|
```bash
|
||||||
|
# 预拉取并打标签
|
||||||
|
REGISTRY_K8S_MIRROR="registry-k8s-io.mirrors.sjtug.sjtu.edu.cn"
|
||||||
|
GHCR_MIRROR="ghcr.tencentcloudcr.com"
|
||||||
|
|
||||||
|
# 预拉取 pause 镜像
|
||||||
|
sudo ctr -n k8s.io images pull ${REGISTRY_K8S_MIRROR}/pause:3.8 || true
|
||||||
|
sudo ctr -n k8s.io images tag ${REGISTRY_K8S_MIRROR}/pause:3.8 registry.k8s.io/pause:3.8 || true
|
||||||
|
|
||||||
|
# 预拉取 flannel 镜像
|
||||||
|
sudo ctr -n k8s.io images pull ${GHCR_MIRROR}/flannel-io/flannel:${FLANNEL_VER} || true
|
||||||
|
sudo ctr -n k8s.io images tag ${GHCR_MIRROR}/flannel-io/flannel:${FLANNEL_VER} ghcr.io/flannel-io/flannel:${FLANNEL_VER} || true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.3 安装 Flannel
|
||||||
|
```bash
|
||||||
|
# 安装 Flannel
|
||||||
|
kubectl apply -f kube-flannel.yml
|
||||||
|
|
||||||
|
# 等待 Flannel 组件就绪
|
||||||
|
kubectl -n kube-flannel rollout status daemonset/kube-flannel-ds --timeout=600s
|
||||||
|
kubectl wait --for=condition=ready pod -l app=flannel -n kube-flannel --timeout=600s
|
||||||
|
|
||||||
|
# 等待 CoreDNS 就绪
|
||||||
|
kubectl -n kube-system rollout status deploy/coredns --timeout=600s
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 7: 节点加入集群
|
||||||
|
|
||||||
|
#### 7.1 节点加入
|
||||||
|
```bash
|
||||||
|
# 检查是否存在加入命令文件
|
||||||
|
if [ ! -f "node-join-command.txt" ]; then
|
||||||
|
echo "错误: 找不到 node-join-command.txt 文件"
|
||||||
|
echo "请先运行 k8s-step4-init-cluster.sh 初始化集群"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 读取加入命令
|
||||||
|
JOIN_COMMAND=$(cat node-join-command.txt)
|
||||||
|
echo "使用加入命令: $JOIN_COMMAND"
|
||||||
|
|
||||||
|
# Node1 加入集群
|
||||||
|
ssh ubuntu@172.17.0.43 "sudo $JOIN_COMMAND"
|
||||||
|
|
||||||
|
# Node2 加入集群
|
||||||
|
ssh ubuntu@172.17.0.34 "sudo $JOIN_COMMAND"
|
||||||
|
|
||||||
|
# 等待节点加入
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# 验证集群状态
|
||||||
|
kubectl get nodes
|
||||||
|
kubectl get pods -n kube-system
|
||||||
|
kubectl get pods -n kube-flannel
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 8: 集群验证
|
||||||
|
|
||||||
|
#### 8.1 验证节点状态
|
||||||
|
```bash
|
||||||
|
kubectl get nodes
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8.2 验证 Pod 状态
|
||||||
|
```bash
|
||||||
|
kubectl get pods -A
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8.3 验证集群功能
|
||||||
|
```bash
|
||||||
|
# 检查集群信息
|
||||||
|
kubectl cluster-info
|
||||||
|
|
||||||
|
# 检查节点详细信息
|
||||||
|
kubectl describe nodes
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 9: 为其他节点安装 kubectl
|
||||||
|
|
||||||
|
#### 9.1 在 node1 和 node2 安装 kubectl
|
||||||
|
```bash
|
||||||
|
# 检查是否已安装
|
||||||
|
if command -v kubectl &> /dev/null; then
|
||||||
|
echo "kubectl 已安装,版本: $(kubectl version --client 2>/dev/null | grep 'Client Version' || echo 'unknown')"
|
||||||
|
echo "跳过安装"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 安装 kubectl
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y apt-transport-https ca-certificates curl
|
||||||
|
|
||||||
|
# 添加 Kubernetes 官方 GPG key
|
||||||
|
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
||||||
|
|
||||||
|
# 添加 Kubernetes apt 仓库
|
||||||
|
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
|
||||||
|
|
||||||
|
# 更新包列表并安装 kubectl
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y kubectl
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 9.2 复制 kubeconfig 配置文件
|
||||||
|
```bash
|
||||||
|
# 在 master 节点执行
|
||||||
|
# 为 node1 创建 .kube 目录
|
||||||
|
ssh ubuntu@172.17.0.43 "mkdir -p ~/.kube"
|
||||||
|
|
||||||
|
# 为 node2 创建 .kube 目录
|
||||||
|
ssh ubuntu@172.17.0.34 "mkdir -p ~/.kube"
|
||||||
|
|
||||||
|
# 复制 kubeconfig 到 node1
|
||||||
|
scp ~/.kube/config ubuntu@172.17.0.43:~/.kube/config
|
||||||
|
|
||||||
|
# 复制 kubeconfig 到 node2
|
||||||
|
scp ~/.kube/config ubuntu@172.17.0.34:~/.kube/config
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 9.3 验证 kubectl 连接
|
||||||
|
```bash
|
||||||
|
# 验证 node1 kubectl 连接
|
||||||
|
ssh ubuntu@172.17.0.43 "kubectl get nodes"
|
||||||
|
|
||||||
|
# 验证 node2 kubectl 连接
|
||||||
|
ssh ubuntu@172.17.0.34 "kubectl get nodes"
|
||||||
|
```
|
||||||
|
|
||||||
48
docs/kubernetes/k8s-prepare-env.sh
Normal file
48
docs/kubernetes/k8s-prepare-env.sh
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "==== Kubernetes 环境准备 ===="
|
||||||
|
|
||||||
|
# 1. 更新系统包
|
||||||
|
echo "更新系统包..."
|
||||||
|
sudo apt update && sudo apt upgrade -y
|
||||||
|
|
||||||
|
# 2. 安装必要的工具
|
||||||
|
echo "安装必要工具..."
|
||||||
|
sudo apt install -y curl wget gnupg lsb-release ca-certificates apt-transport-https software-properties-common
|
||||||
|
|
||||||
|
# 3. 禁用 swap
|
||||||
|
echo "禁用 swap..."
|
||||||
|
sudo swapoff -a
|
||||||
|
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
|
||||||
|
|
||||||
|
# 4. 配置内核参数
|
||||||
|
echo "配置内核参数..."
|
||||||
|
cat <<EOF_MODULES | sudo tee /etc/modules-load.d/k8s.conf
|
||||||
|
overlay
|
||||||
|
br_netfilter
|
||||||
|
EOF_MODULES
|
||||||
|
|
||||||
|
sudo modprobe overlay
|
||||||
|
sudo modprobe br_netfilter
|
||||||
|
|
||||||
|
# 5. 配置 sysctl 参数
|
||||||
|
echo "配置 sysctl 参数..."
|
||||||
|
cat <<EOF_SYSCTL | sudo tee /etc/sysctl.d/k8s.conf
|
||||||
|
net.bridge.bridge-nf-call-iptables = 1
|
||||||
|
net.bridge.bridge-nf-call-ip6tables = 1
|
||||||
|
net.ipv4.ip_forward = 1
|
||||||
|
EOF_SYSCTL
|
||||||
|
|
||||||
|
sudo sysctl --system
|
||||||
|
|
||||||
|
# 6. 配置防火墙
|
||||||
|
echo "配置防火墙..."
|
||||||
|
sudo ufw --force disable || true
|
||||||
|
|
||||||
|
# 按你的要求,不在节点上修改 /etc/hosts
|
||||||
|
|
||||||
|
echo "==== 环境准备完成 ===="
|
||||||
|
echo "当前主机名: $(hostname)"
|
||||||
|
echo "当前 IP: $(ip route get 1 | awk '{print $7; exit}')"
|
||||||
|
echo "Swap 状态: $(swapon --show | wc -l) 个 swap 分区"
|
||||||
109
docs/kubernetes/k8s-step1-prepare-env.sh
Normal file
109
docs/kubernetes/k8s-step1-prepare-env.sh
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Kubernetes 环境准备脚本
|
||||||
|
# 功能: 在所有节点准备 Kubernetes 运行环境
|
||||||
|
|
||||||
|
echo "==== Kubernetes 环境准备 ===="
|
||||||
|
|
||||||
|
# 定义节点列表
|
||||||
|
NODES=("172.17.0.15:master" "172.17.0.43:node1" "172.17.0.34:node2")
|
||||||
|
|
||||||
|
# 本机 IP 与 SSH 选项
|
||||||
|
LOCAL_IP=$(ip route get 1 | awk '{print $7; exit}')
|
||||||
|
SSH_OPTS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
|
||||||
|
# SSH 私钥(可用环境变量 SSH_KEY 覆盖),存在则自动携带
|
||||||
|
SSH_KEY_PATH=${SSH_KEY:-$HOME/.ssh/id_rsa}
|
||||||
|
[ -f "$SSH_KEY_PATH" ] && SSH_ID="-i $SSH_KEY_PATH" || SSH_ID=""
|
||||||
|
|
||||||
|
# 函数:在所有节点执行命令
|
||||||
|
execute_on_all_nodes() {
|
||||||
|
local command="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
echo "==== $description ===="
|
||||||
|
for node in "${NODES[@]}"; do
|
||||||
|
IFS=':' read -r ip hostname <<< "$node"
|
||||||
|
echo "在 $hostname ($ip) 执行: $command"
|
||||||
|
if [ "$ip" = "$LOCAL_IP" ] || [ "$hostname" = "master" ]; then
|
||||||
|
bash -lc "$command"
|
||||||
|
else
|
||||||
|
ssh $SSH_OPTS $SSH_ID ubuntu@$ip "$command"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 函数:传输文件到所有节点
|
||||||
|
copy_to_all_nodes() {
|
||||||
|
local file="$1"
|
||||||
|
echo "==== 传输文件 $file 到所有节点 ===="
|
||||||
|
for node in "${NODES[@]}"; do
|
||||||
|
IFS=':' read -r ip hostname <<< "$node"
|
||||||
|
echo "传输到 $hostname ($ip)"
|
||||||
|
if [ "$ip" = "$LOCAL_IP" ] || [ "$hostname" = "master" ]; then
|
||||||
|
cp -f "$file" ~/
|
||||||
|
else
|
||||||
|
scp $SSH_OPTS $SSH_ID "$file" ubuntu@$ip:~/
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建环境准备脚本
|
||||||
|
cat > k8s-prepare-env.sh << 'EOF_OUTER'
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "==== Kubernetes 环境准备 ===="
|
||||||
|
|
||||||
|
# 1. 更新系统包
|
||||||
|
echo "更新系统包..."
|
||||||
|
sudo apt update && sudo apt upgrade -y
|
||||||
|
|
||||||
|
# 2. 安装必要的工具
|
||||||
|
echo "安装必要工具..."
|
||||||
|
sudo apt install -y curl wget gnupg lsb-release ca-certificates apt-transport-https software-properties-common
|
||||||
|
|
||||||
|
# 3. 禁用 swap
|
||||||
|
echo "禁用 swap..."
|
||||||
|
sudo swapoff -a
|
||||||
|
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
|
||||||
|
|
||||||
|
# 4. 配置内核参数
|
||||||
|
echo "配置内核参数..."
|
||||||
|
cat <<EOF_MODULES | sudo tee /etc/modules-load.d/k8s.conf
|
||||||
|
overlay
|
||||||
|
br_netfilter
|
||||||
|
EOF_MODULES
|
||||||
|
|
||||||
|
sudo modprobe overlay
|
||||||
|
sudo modprobe br_netfilter
|
||||||
|
|
||||||
|
# 5. 配置 sysctl 参数
|
||||||
|
echo "配置 sysctl 参数..."
|
||||||
|
cat <<EOF_SYSCTL | sudo tee /etc/sysctl.d/k8s.conf
|
||||||
|
net.bridge.bridge-nf-call-iptables = 1
|
||||||
|
net.bridge.bridge-nf-call-ip6tables = 1
|
||||||
|
net.ipv4.ip_forward = 1
|
||||||
|
EOF_SYSCTL
|
||||||
|
|
||||||
|
sudo sysctl --system
|
||||||
|
|
||||||
|
# 6. 配置防火墙
|
||||||
|
echo "配置防火墙..."
|
||||||
|
sudo ufw --force disable || true
|
||||||
|
|
||||||
|
# 按你的要求,不在节点上修改 /etc/hosts
|
||||||
|
|
||||||
|
echo "==== 环境准备完成 ===="
|
||||||
|
echo "当前主机名: $(hostname)"
|
||||||
|
echo "当前 IP: $(ip route get 1 | awk '{print $7; exit}')"
|
||||||
|
echo "Swap 状态: $(swapon --show | wc -l) 个 swap 分区"
|
||||||
|
EOF_OUTER
|
||||||
|
|
||||||
|
chmod +x k8s-prepare-env.sh
|
||||||
|
copy_to_all_nodes k8s-prepare-env.sh
|
||||||
|
execute_on_all_nodes "./k8s-prepare-env.sh" "环境准备"
|
||||||
|
|
||||||
|
echo "==== 环境准备完成 ===="
|
||||||
133
docs/kubernetes/k8s-step2-install-containerd.sh
Normal file
133
docs/kubernetes/k8s-step2-install-containerd.sh
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Kubernetes 容器运行时安装脚本
|
||||||
|
# 功能: 在所有节点安装 containerd 和 CNI 插件
|
||||||
|
|
||||||
|
echo "==== 安装容器运行时 (containerd) ===="
|
||||||
|
|
||||||
|
# 定义节点列表
|
||||||
|
NODES=("172.17.0.15:master" "172.17.0.43:node1" "172.17.0.34:node2")
|
||||||
|
|
||||||
|
# 本机 IP 与 SSH 选项
|
||||||
|
LOCAL_IP=$(ip route get 1 | awk '{print $7; exit}')
|
||||||
|
SSH_OPTS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
|
||||||
|
# SSH 私钥(可用环境变量 SSH_KEY 覆盖),存在则自动携带
|
||||||
|
SSH_KEY_PATH=${SSH_KEY:-$HOME/.ssh/id_rsa}
|
||||||
|
[ -f "$SSH_KEY_PATH" ] && SSH_ID="-i $SSH_KEY_PATH" || SSH_ID=""
|
||||||
|
|
||||||
|
# 统一的工件目录与文件名(在 master 上下载一次后分发)
|
||||||
|
ARTIFACTS_DIR="$HOME/k8s-artifacts"
|
||||||
|
CNI_VERSION="v1.3.0"
|
||||||
|
CNI_TGZ="cni-plugins-linux-amd64-${CNI_VERSION}.tgz"
|
||||||
|
|
||||||
|
# 函数:在所有节点执行命令
|
||||||
|
execute_on_all_nodes() {
|
||||||
|
local command="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
echo "==== $description ===="
|
||||||
|
for node in "${NODES[@]}"; do
|
||||||
|
IFS=':' read -r ip hostname <<< "$node"
|
||||||
|
echo "在 $hostname ($ip) 执行: $command"
|
||||||
|
if [ "$ip" = "$LOCAL_IP" ] || [ "$hostname" = "master" ]; then
|
||||||
|
bash -lc "$command"
|
||||||
|
else
|
||||||
|
ssh $SSH_OPTS $SSH_ID ubuntu@$ip "$command"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 函数:传输文件到所有节点
|
||||||
|
copy_to_all_nodes() {
|
||||||
|
local file="$1"
|
||||||
|
echo "==== 传输文件 $file 到所有节点 ===="
|
||||||
|
for node in "${NODES[@]}"; do
|
||||||
|
IFS=':' read -r ip hostname <<< "$node"
|
||||||
|
echo "传输到 $hostname ($ip)"
|
||||||
|
if [ "$ip" = "$LOCAL_IP" ] || [ "$hostname" = "master" ]; then
|
||||||
|
cp -f "$file" ~/
|
||||||
|
else
|
||||||
|
scp $SSH_OPTS $SSH_ID "$file" ubuntu@$ip:~/
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建容器运行时安装脚本
|
||||||
|
cat > k8s-install-containerd.sh << 'EOF_OUTER'
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "==== 安装容器运行时 (containerd) ===="
|
||||||
|
|
||||||
|
# 1. 安装 containerd
|
||||||
|
echo "安装 containerd..."
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y containerd
|
||||||
|
|
||||||
|
# 2. 配置 containerd
|
||||||
|
echo "配置 containerd..."
|
||||||
|
# ① 停止 containerd
|
||||||
|
sudo systemctl stop containerd
|
||||||
|
|
||||||
|
# ② 生成默认配置
|
||||||
|
sudo mkdir -p /etc/containerd
|
||||||
|
sudo containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
|
||||||
|
|
||||||
|
# ③ 注入镜像加速配置(docker.io/quay.io:腾讯云,其它:高校镜像优先)
|
||||||
|
sudo sed -i '/\[plugins."io.containerd.grpc.v1.cri".registry.mirrors\]/a\
|
||||||
|
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]\n endpoint = ["https://mirror.ccs.tencentyun.com"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."quay.io"]\n endpoint = ["https://quay.tencentcloudcr.com"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."ghcr.io"]\n endpoint = ["https://ghcr.nju.edu.cn"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]\n endpoint = ["https://gcr.nju.edu.cn"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]\n endpoint = ["https://registry-k8s-io.mirrors.sjtug.sjtu.edu.cn"]\n [plugins."io.containerd.grpc.v1.cri".registry.mirrors."gcr.io"]\n endpoint = ["https://gcr.nju.edu.cn"]' /etc/containerd/config.toml
|
||||||
|
|
||||||
|
# ④ 重新加载并启动 containerd
|
||||||
|
sudo systemctl daemon-reexec
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl restart containerd
|
||||||
|
|
||||||
|
## 4. 在 master 预下载 CNI 压缩包并分发到各节点
|
||||||
|
echo "准备 CNI 工件并分发..."
|
||||||
|
if [ "$LOCAL_IP" = "172.17.0.15" ]; then
|
||||||
|
mkdir -p "$ARTIFACTS_DIR"
|
||||||
|
if [ ! -f "$ARTIFACTS_DIR/$CNI_TGZ" ]; then
|
||||||
|
echo "在 master 下载 $CNI_TGZ ..."
|
||||||
|
curl -L --fail --retry 3 --connect-timeout 10 \
|
||||||
|
-o "$ARTIFACTS_DIR/$CNI_TGZ" \
|
||||||
|
"https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/$CNI_TGZ"
|
||||||
|
else
|
||||||
|
echo "已存在 $ARTIFACTS_DIR/$CNI_TGZ,跳过下载"
|
||||||
|
fi
|
||||||
|
# 分发到所有节点 home 目录
|
||||||
|
copy_to_all_nodes "$ARTIFACTS_DIR/$CNI_TGZ"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. 安装 CNI 插件(优先使用已分发的本地文件)
|
||||||
|
echo "安装 CNI 插件..."
|
||||||
|
sudo mkdir -p /opt/cni/bin
|
||||||
|
if [ -f "$CNI_TGZ" ]; then
|
||||||
|
echo "使用已分发的 $CNI_TGZ 进行安装"
|
||||||
|
sudo tar -xzf "$CNI_TGZ" -C /opt/cni/bin/
|
||||||
|
rm -f "$CNI_TGZ"
|
||||||
|
else
|
||||||
|
echo "未找到本地 $CNI_TGZ,尝试在线下载(网络慢时可能用时较长)..."
|
||||||
|
curl -L --fail --retry 3 --connect-timeout 10 \
|
||||||
|
-o "$CNI_TGZ" \
|
||||||
|
"https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/$CNI_TGZ"
|
||||||
|
sudo tar -xzf "$CNI_TGZ" -C /opt/cni/bin/
|
||||||
|
rm -f "$CNI_TGZ"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. 验证安装
|
||||||
|
echo "==== 验证 containerd 安装 ===="
|
||||||
|
sudo systemctl status containerd --no-pager -l
|
||||||
|
sudo ctr version
|
||||||
|
|
||||||
|
echo "==== containerd 安装完成 ===="
|
||||||
|
EOF_OUTER
|
||||||
|
|
||||||
|
chmod +x k8s-install-containerd.sh
|
||||||
|
copy_to_all_nodes k8s-install-containerd.sh
|
||||||
|
execute_on_all_nodes "./k8s-install-containerd.sh" "安装容器运行时"
|
||||||
|
|
||||||
|
echo "==== 容器运行时安装完成 ===="
|
||||||
|
|
||||||
149
docs/kubernetes/k8s-step3-install-components.sh
Normal file
149
docs/kubernetes/k8s-step3-install-components.sh
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Kubernetes 组件安装脚本
|
||||||
|
# 功能: 在所有节点安装 kubelet, kubeadm, kubectl
|
||||||
|
|
||||||
|
echo "==== 安装 Kubernetes 组件 ===="
|
||||||
|
|
||||||
|
# 定义节点列表
|
||||||
|
NODES=("172.17.0.15:master" "172.17.0.43:node1" "172.17.0.34:node2")
|
||||||
|
|
||||||
|
# 本机 IP 与 SSH 选项
|
||||||
|
LOCAL_IP=$(ip route get 1 | awk '{print $7; exit}')
|
||||||
|
SSH_OPTS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
|
||||||
|
# SSH 私钥(可用环境变量 SSH_KEY 覆盖),存在则自动携带
|
||||||
|
SSH_KEY_PATH=${SSH_KEY:-$HOME/.ssh/id_rsa}
|
||||||
|
[ -f "$SSH_KEY_PATH" ] && SSH_ID="-i $SSH_KEY_PATH" || SSH_ID=""
|
||||||
|
|
||||||
|
# 函数:在所有节点执行命令
|
||||||
|
execute_on_all_nodes() {
|
||||||
|
local command="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
echo "==== $description ===="
|
||||||
|
for node in "${NODES[@]}"; do
|
||||||
|
IFS=':' read -r ip hostname <<< "$node"
|
||||||
|
echo "在 $hostname ($ip) 执行: $command"
|
||||||
|
if [ "$ip" = "$LOCAL_IP" ] || [ "$hostname" = "master" ]; then
|
||||||
|
bash -lc "$command"
|
||||||
|
else
|
||||||
|
ssh $SSH_OPTS $SSH_ID ubuntu@$ip "$command"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 函数:传输文件到所有节点
|
||||||
|
copy_to_all_nodes() {
|
||||||
|
local file="$1"
|
||||||
|
echo "==== 传输文件 $file 到所有节点 ===="
|
||||||
|
for node in "${NODES[@]}"; do
|
||||||
|
IFS=':' read -r ip hostname <<< "$node"
|
||||||
|
echo "传输到 $hostname ($ip)"
|
||||||
|
if [ "$ip" = "$LOCAL_IP" ] || [ "$hostname" = "master" ]; then
|
||||||
|
cp -f "$file" ~/
|
||||||
|
else
|
||||||
|
scp $SSH_OPTS $SSH_ID "$file" ubuntu@$ip:~/
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建 Kubernetes 组件安装脚本
|
||||||
|
cat > k8s-install-components.sh << 'EOF_OUTER'
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "==== 安装 Kubernetes 组件 ===="
|
||||||
|
|
||||||
|
# 1. 添加 Kubernetes 仓库
|
||||||
|
echo "添加 Kubernetes 仓库 (pkgs.k8s.io v1.32)..."
|
||||||
|
# 确保 keyrings 目录存在并可读
|
||||||
|
sudo install -m 0755 -d /etc/apt/keyrings
|
||||||
|
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
||||||
|
sudo chmod a+r /etc/apt/keyrings/kubernetes-apt-keyring.gpg
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list >/dev/null
|
||||||
|
|
||||||
|
# 2. 更新包列表
|
||||||
|
echo "更新包列表..."
|
||||||
|
sudo apt update
|
||||||
|
|
||||||
|
# 3. 安装 Kubernetes 组件(使用 v1.32 通道的最新补丁版本)
|
||||||
|
echo "安装 Kubernetes 组件..."
|
||||||
|
sudo apt install -y kubelet kubeadm kubectl
|
||||||
|
|
||||||
|
# 4. 锁定版本防止自动更新
|
||||||
|
echo "锁定 Kubernetes 版本..."
|
||||||
|
sudo apt-mark hold kubelet kubeadm kubectl
|
||||||
|
|
||||||
|
# 5. 配置 kubelet
|
||||||
|
echo "配置 kubelet..."
|
||||||
|
sudo mkdir -p /var/lib/kubelet
|
||||||
|
cat <<EOF_KUBELET | sudo tee /var/lib/kubelet/config.yaml
|
||||||
|
apiVersion: kubelet.config.k8s.io/v1beta1
|
||||||
|
kind: KubeletConfiguration
|
||||||
|
authentication:
|
||||||
|
anonymous:
|
||||||
|
enabled: false
|
||||||
|
webhook:
|
||||||
|
enabled: true
|
||||||
|
x509:
|
||||||
|
clientCAFile: /etc/kubernetes/pki/ca.crt
|
||||||
|
authorization:
|
||||||
|
mode: Webhook
|
||||||
|
clusterDomain: cluster.local
|
||||||
|
clusterDNS:
|
||||||
|
- 10.96.0.10
|
||||||
|
containerRuntimeEndpoint: unix:///var/run/containerd/containerd.sock
|
||||||
|
cgroupDriver: systemd
|
||||||
|
failSwapOn: false
|
||||||
|
hairpinMode: promiscuous-bridge
|
||||||
|
healthzBindAddress: 127.0.0.1
|
||||||
|
healthzPort: 10248
|
||||||
|
httpCheckFrequency: 20s
|
||||||
|
imageMinimumGCAge: 2m0s
|
||||||
|
imageGCHighThresholdPercent: 85
|
||||||
|
imageGCLowThresholdPercent: 80
|
||||||
|
iptablesDropBit: 15
|
||||||
|
iptablesMasqueradeBit: 15
|
||||||
|
kubeAPIBurst: 10
|
||||||
|
kubeAPIQPS: 5
|
||||||
|
makeIPTablesUtilChains: true
|
||||||
|
maxOpenFiles: 1000000
|
||||||
|
maxPods: 110
|
||||||
|
nodeStatusUpdateFrequency: 10s
|
||||||
|
oomScoreAdj: -999
|
||||||
|
podCIDR: 10.244.0.0/16
|
||||||
|
registryBurst: 10
|
||||||
|
registryPullQPS: 5
|
||||||
|
resolvConf: /etc/resolv.conf
|
||||||
|
rotateCertificates: true
|
||||||
|
runtimeRequestTimeout: 2m0s
|
||||||
|
serializeImagePulls: true
|
||||||
|
serverTLSBootstrap: true
|
||||||
|
streamingConnectionIdleTimeout: 4h0m0s
|
||||||
|
syncFrequency: 1m0s
|
||||||
|
volumeStatsAggPeriod: 1m0s
|
||||||
|
EOF_KUBELET
|
||||||
|
|
||||||
|
# 6. 启动 kubelet
|
||||||
|
echo "启动 kubelet..."
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable kubelet
|
||||||
|
sudo systemctl start kubelet
|
||||||
|
|
||||||
|
# 7. 验证安装
|
||||||
|
echo "==== 验证 Kubernetes 组件安装 ===="
|
||||||
|
kubelet --version
|
||||||
|
kubeadm version
|
||||||
|
kubectl version --client
|
||||||
|
|
||||||
|
echo "==== Kubernetes 组件安装完成 ===="
|
||||||
|
EOF_OUTER
|
||||||
|
|
||||||
|
chmod +x k8s-install-components.sh
|
||||||
|
copy_to_all_nodes k8s-install-components.sh
|
||||||
|
execute_on_all_nodes "./k8s-install-components.sh" "安装 Kubernetes 组件"
|
||||||
|
|
||||||
|
echo "==== Kubernetes 组件安装完成 ===="
|
||||||
40
docs/kubernetes/k8s-step4-init-cluster.sh
Normal file
40
docs/kubernetes/k8s-step4-init-cluster.sh
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Kubernetes 集群初始化脚本
|
||||||
|
# 功能: 在 Master 节点初始化 Kubernetes 集群
|
||||||
|
|
||||||
|
echo "==== 初始化 Kubernetes 集群 ===="
|
||||||
|
|
||||||
|
# 1. 初始化集群
|
||||||
|
echo "初始化 Kubernetes 集群..."
|
||||||
|
sudo kubeadm init \
|
||||||
|
--apiserver-advertise-address=172.17.0.15 \
|
||||||
|
--control-plane-endpoint=172.17.0.15:6443 \
|
||||||
|
--kubernetes-version=v1.32.3 \
|
||||||
|
--service-cidr=10.96.0.0/12 \
|
||||||
|
--pod-network-cidr=10.244.0.0/16 \
|
||||||
|
--image-repository=registry.aliyuncs.com/google_containers \
|
||||||
|
--upload-certs \
|
||||||
|
--ignore-preflight-errors=Swap
|
||||||
|
|
||||||
|
# 2. 配置 kubectl
|
||||||
|
echo "配置 kubectl..."
|
||||||
|
mkdir -p $HOME/.kube
|
||||||
|
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
|
||||||
|
sudo chown $(id -u):$(id -g) $HOME/.kube/config
|
||||||
|
|
||||||
|
# 3. 生成节点加入命令
|
||||||
|
echo "生成节点加入命令..."
|
||||||
|
JOIN_COMMAND=$(kubeadm token create --print-join-command)
|
||||||
|
echo "节点加入命令:"
|
||||||
|
echo "$JOIN_COMMAND"
|
||||||
|
echo "$JOIN_COMMAND" > node-join-command.txt
|
||||||
|
|
||||||
|
# 4. 验证集群状态
|
||||||
|
echo "==== 验证集群状态 ===="
|
||||||
|
kubectl get nodes
|
||||||
|
kubectl get pods -n kube-system
|
||||||
|
|
||||||
|
echo "==== 集群初始化完成 ===="
|
||||||
|
echo "请保存节点加入命令,稍后用于将 node1 和 node2 加入集群"
|
||||||
77
docs/kubernetes/k8s-step5-install-flannel.sh
Normal file
77
docs/kubernetes/k8s-step5-install-flannel.sh
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Kubernetes 网络插件安装脚本
|
||||||
|
# 功能: 在 Master 节点安装 Flannel 网络插件
|
||||||
|
|
||||||
|
echo "==== 安装 Flannel 网络插件 ===="
|
||||||
|
|
||||||
|
# 1. 下载 Flannel 配置文件
|
||||||
|
echo "下载 Flannel 配置文件..."
|
||||||
|
FLANNEL_VER="v0.27.4"
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/flannel-io/flannel/${FLANNEL_VER}/Documentation/kube-flannel.yml -O
|
||||||
|
|
||||||
|
# 2. 修改 Flannel 配置
|
||||||
|
echo "修改 Flannel 配置..."
|
||||||
|
sed -i 's|"Network": "10.244.0.0/16"|"Network": "10.244.0.0/16"|g' kube-flannel.yml
|
||||||
|
|
||||||
|
echo "预拉取 Flannel 相关镜像(优先国内镜像域名,拉取后回标官方名)..."
|
||||||
|
DOCKER_MIRROR="docker.m.daocloud.io"
|
||||||
|
REGISTRY_K8S_MIRROR="registry-k8s-io.mirrors.sjtug.sjtu.edu.cn"
|
||||||
|
GHCR_MIRROR="ghcr.tencentcloudcr.com"
|
||||||
|
|
||||||
|
IMAGES=(
|
||||||
|
"registry.k8s.io/pause:3.8"
|
||||||
|
"ghcr.io/flannel-io/flannel:${FLANNEL_VER}"
|
||||||
|
)
|
||||||
|
|
||||||
|
pull_and_tag() {
|
||||||
|
local origin_ref="$1" # e.g. registry.k8s.io/pause:3.8
|
||||||
|
local mirror_ref="$2" # e.g. registry-k8s-io.mirrors.sjtug.sjtu.edu.cn/pause:3.8
|
||||||
|
echo "尝试从镜像 ${mirror_ref} 预拉取..."
|
||||||
|
for i in $(seq 1 5); do
|
||||||
|
if sudo ctr -n k8s.io images pull "${mirror_ref}"; then
|
||||||
|
echo "打官方标签: ${origin_ref} <- ${mirror_ref}"
|
||||||
|
sudo ctr -n k8s.io images tag "${mirror_ref}" "${origin_ref}" || true
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "pull 失败,重试 ${i}/5..."; sleep 2
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 预拉取 pause 镜像
|
||||||
|
echo "预拉取: registry.k8s.io/pause:3.8"
|
||||||
|
if pull_and_tag "registry.k8s.io/pause:3.8" "${REGISTRY_K8S_MIRROR}/pause:3.8"; then
|
||||||
|
echo "pause 镜像拉取成功"
|
||||||
|
else
|
||||||
|
echo "WARN: pause 镜像拉取失败,将由 kubelet 重试"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 预拉取 flannel 镜像
|
||||||
|
echo "预拉取: ghcr.io/flannel-io/flannel:${FLANNEL_VER}"
|
||||||
|
if pull_and_tag "ghcr.io/flannel-io/flannel:${FLANNEL_VER}" "${GHCR_MIRROR}/flannel-io/flannel:${FLANNEL_VER}"; then
|
||||||
|
echo "flannel 镜像拉取成功"
|
||||||
|
else
|
||||||
|
echo "WARN: flannel 镜像拉取失败,将由 kubelet 重试"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. 安装 Flannel
|
||||||
|
echo "安装 Flannel..."
|
||||||
|
kubectl apply -f kube-flannel.yml
|
||||||
|
|
||||||
|
# 4. 等待 Flannel 启动
|
||||||
|
echo "等待 Flannel 组件就绪..."
|
||||||
|
kubectl -n kube-flannel rollout status daemonset/kube-flannel-ds --timeout=600s || true
|
||||||
|
kubectl wait --for=condition=ready pod -l app=flannel -n kube-flannel --timeout=600s || true
|
||||||
|
|
||||||
|
echo "等待 CoreDNS 由 Pending 变为 Ready..."
|
||||||
|
kubectl -n kube-system rollout status deploy/coredns --timeout=600s || true
|
||||||
|
|
||||||
|
# 5. 验证网络插件
|
||||||
|
echo "==== 验证 Flannel 安装 ===="
|
||||||
|
kubectl get pods -n kube-flannel
|
||||||
|
kubectl get nodes
|
||||||
|
|
||||||
|
echo "==== Flannel 网络插件安装完成 ===="
|
||||||
|
|
||||||
53
docs/kubernetes/k8s-step6-join-nodes.sh
Normal file
53
docs/kubernetes/k8s-step6-join-nodes.sh
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Kubernetes 节点加入脚本
|
||||||
|
# 功能: 将 Node1 和 Node2 加入 Kubernetes 集群
|
||||||
|
|
||||||
|
echo "==== 将节点加入 Kubernetes 集群 ===="
|
||||||
|
|
||||||
|
# 检查是否存在加入命令文件
|
||||||
|
if [ ! -f "node-join-command.txt" ]; then
|
||||||
|
echo "错误: 找不到 node-join-command.txt 文件"
|
||||||
|
echo "请先运行 k8s-step4-init-cluster.sh 初始化集群"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 读取加入命令
|
||||||
|
JOIN_COMMAND=$(cat node-join-command.txt)
|
||||||
|
|
||||||
|
# SSH 选项与密钥
|
||||||
|
SSH_OPTS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
|
||||||
|
SSH_KEY_PATH=${SSH_KEY:-$HOME/.ssh/id_rsa}
|
||||||
|
[ -f "$SSH_KEY_PATH" ] && SSH_ID="-i $SSH_KEY_PATH" || SSH_ID=""
|
||||||
|
echo "使用加入命令: $JOIN_COMMAND"
|
||||||
|
|
||||||
|
# 定义节点列表
|
||||||
|
NODES=("172.17.0.43:node1" "172.17.0.34:node2")
|
||||||
|
|
||||||
|
# 将节点加入集群
|
||||||
|
for node in "${NODES[@]}"; do
|
||||||
|
IFS=':' read -r ip hostname <<< "$node"
|
||||||
|
echo "==== 将 $hostname ($ip) 加入集群 ===="
|
||||||
|
ssh $SSH_OPTS $SSH_ID ubuntu@$ip "sudo $JOIN_COMMAND"
|
||||||
|
echo "$hostname 加入完成"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 等待节点加入
|
||||||
|
echo "==== 等待节点加入集群 ===="
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# 验证集群状态
|
||||||
|
echo "==== 验证集群状态 ===="
|
||||||
|
kubectl get nodes
|
||||||
|
kubectl get pods -n kube-system
|
||||||
|
kubectl get pods -n kube-flannel
|
||||||
|
|
||||||
|
echo "==== 节点加入完成 ===="
|
||||||
|
echo "集群信息:"
|
||||||
|
echo "- Master: 172.17.0.15"
|
||||||
|
echo "- Node1: 172.17.0.43"
|
||||||
|
echo "- Node2: 172.17.0.34"
|
||||||
|
echo "- Kubernetes 版本: v1.32.3"
|
||||||
|
echo "- 网络插件: Flannel"
|
||||||
|
echo "- 容器运行时: containerd"
|
||||||
211
docs/kubernetes/kube-flannel.yml
Normal file
211
docs/kubernetes/kube-flannel.yml
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
---
|
||||||
|
kind: Namespace
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: kube-flannel
|
||||||
|
labels:
|
||||||
|
k8s-app: flannel
|
||||||
|
pod-security.kubernetes.io/enforce: privileged
|
||||||
|
---
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: flannel
|
||||||
|
name: flannel
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes/status
|
||||||
|
verbs:
|
||||||
|
- patch
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: flannel
|
||||||
|
name: flannel
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: flannel
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: flannel
|
||||||
|
namespace: kube-flannel
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: flannel
|
||||||
|
name: flannel
|
||||||
|
namespace: kube-flannel
|
||||||
|
---
|
||||||
|
kind: ConfigMap
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: kube-flannel-cfg
|
||||||
|
namespace: kube-flannel
|
||||||
|
labels:
|
||||||
|
tier: node
|
||||||
|
k8s-app: flannel
|
||||||
|
app: flannel
|
||||||
|
data:
|
||||||
|
cni-conf.json: |
|
||||||
|
{
|
||||||
|
"name": "cbr0",
|
||||||
|
"cniVersion": "0.3.1",
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"type": "flannel",
|
||||||
|
"delegate": {
|
||||||
|
"hairpinMode": true,
|
||||||
|
"isDefaultGateway": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "portmap",
|
||||||
|
"capabilities": {
|
||||||
|
"portMappings": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
net-conf.json: |
|
||||||
|
{
|
||||||
|
"Network": "10.244.0.0/16",
|
||||||
|
"EnableNFTables": false,
|
||||||
|
"Backend": {
|
||||||
|
"Type": "vxlan"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: kube-flannel-ds
|
||||||
|
namespace: kube-flannel
|
||||||
|
labels:
|
||||||
|
tier: node
|
||||||
|
app: flannel
|
||||||
|
k8s-app: flannel
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: flannel
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
tier: node
|
||||||
|
app: flannel
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/os
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- linux
|
||||||
|
hostNetwork: true
|
||||||
|
priorityClassName: system-node-critical
|
||||||
|
tolerations:
|
||||||
|
- operator: Exists
|
||||||
|
effect: NoSchedule
|
||||||
|
serviceAccountName: flannel
|
||||||
|
initContainers:
|
||||||
|
- name: install-cni-plugin
|
||||||
|
image: ghcr.io/flannel-io/flannel-cni-plugin:v1.8.0-flannel1
|
||||||
|
command:
|
||||||
|
- cp
|
||||||
|
args:
|
||||||
|
- -f
|
||||||
|
- /flannel
|
||||||
|
- /opt/cni/bin/flannel
|
||||||
|
volumeMounts:
|
||||||
|
- name: cni-plugin
|
||||||
|
mountPath: /opt/cni/bin
|
||||||
|
- name: install-cni
|
||||||
|
image: ghcr.io/flannel-io/flannel:v0.27.4
|
||||||
|
command:
|
||||||
|
- cp
|
||||||
|
args:
|
||||||
|
- -f
|
||||||
|
- /etc/kube-flannel/cni-conf.json
|
||||||
|
- /etc/cni/net.d/10-flannel.conflist
|
||||||
|
volumeMounts:
|
||||||
|
- name: cni
|
||||||
|
mountPath: /etc/cni/net.d
|
||||||
|
- name: flannel-cfg
|
||||||
|
mountPath: /etc/kube-flannel/
|
||||||
|
containers:
|
||||||
|
- name: kube-flannel
|
||||||
|
image: ghcr.io/flannel-io/flannel:v0.27.4
|
||||||
|
command:
|
||||||
|
- /opt/bin/flanneld
|
||||||
|
args:
|
||||||
|
- --ip-masq
|
||||||
|
- --kube-subnet-mgr
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "100m"
|
||||||
|
memory: "50Mi"
|
||||||
|
securityContext:
|
||||||
|
privileged: false
|
||||||
|
capabilities:
|
||||||
|
add: ["NET_ADMIN", "NET_RAW"]
|
||||||
|
env:
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
- name: POD_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
- name: EVENT_QUEUE_DEPTH
|
||||||
|
value: "5000"
|
||||||
|
- name: CONT_WHEN_CACHE_NOT_READY
|
||||||
|
value: "false"
|
||||||
|
volumeMounts:
|
||||||
|
- name: run
|
||||||
|
mountPath: /run/flannel
|
||||||
|
- name: flannel-cfg
|
||||||
|
mountPath: /etc/kube-flannel/
|
||||||
|
- name: xtables-lock
|
||||||
|
mountPath: /run/xtables.lock
|
||||||
|
volumes:
|
||||||
|
- name: run
|
||||||
|
hostPath:
|
||||||
|
path: /run/flannel
|
||||||
|
- name: cni-plugin
|
||||||
|
hostPath:
|
||||||
|
path: /opt/cni/bin
|
||||||
|
- name: cni
|
||||||
|
hostPath:
|
||||||
|
path: /etc/cni/net.d
|
||||||
|
- name: flannel-cfg
|
||||||
|
configMap:
|
||||||
|
name: kube-flannel-cfg
|
||||||
|
- name: xtables-lock
|
||||||
|
hostPath:
|
||||||
|
path: /run/xtables.lock
|
||||||
|
type: FileOrCreate
|
||||||
51
docs/kubernetes/setup-master-gateway.sh
Normal file
51
docs/kubernetes/setup-master-gateway.sh
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "==== 配置 Master 节点作为网关 ===="
|
||||||
|
|
||||||
|
# 1. 启用 IP 转发
|
||||||
|
echo "启用 IP 转发..."
|
||||||
|
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
|
||||||
|
sudo sysctl -p
|
||||||
|
|
||||||
|
# 2. 配置 iptables NAT 规则
|
||||||
|
echo "配置 iptables NAT 规则..."
|
||||||
|
# 清空现有规则
|
||||||
|
sudo iptables -F
|
||||||
|
sudo iptables -t nat -F
|
||||||
|
sudo iptables -t mangle -F
|
||||||
|
sudo iptables -X
|
||||||
|
sudo iptables -t nat -X
|
||||||
|
sudo iptables -t mangle -X
|
||||||
|
|
||||||
|
# 设置默认策略
|
||||||
|
sudo iptables -P INPUT ACCEPT
|
||||||
|
sudo iptables -P FORWARD ACCEPT
|
||||||
|
sudo iptables -P OUTPUT ACCEPT
|
||||||
|
|
||||||
|
# 配置 NAT 规则 - 允许内网节点通过 master 访问外网
|
||||||
|
sudo iptables -t nat -A POSTROUTING -s 172.17.0.0/20 -o eth0 -j MASQUERADE
|
||||||
|
|
||||||
|
# 允许转发来自内网的流量
|
||||||
|
sudo iptables -A FORWARD -s 172.17.0.0/20 -j ACCEPT
|
||||||
|
sudo iptables -A FORWARD -d 172.17.0.0/20 -j ACCEPT
|
||||||
|
|
||||||
|
# 3. 保存 iptables 规则
|
||||||
|
echo "保存 iptables 规则..."
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y iptables-persistent
|
||||||
|
sudo netfilter-persistent save
|
||||||
|
|
||||||
|
# 4. 验证配置
|
||||||
|
echo "==== 验证配置 ===="
|
||||||
|
echo "IP 转发状态:"
|
||||||
|
cat /proc/sys/net/ipv4/ip_forward
|
||||||
|
|
||||||
|
echo "当前 iptables NAT 规则:"
|
||||||
|
sudo iptables -t nat -L -n -v
|
||||||
|
|
||||||
|
echo "当前 iptables FORWARD 规则:"
|
||||||
|
sudo iptables -L FORWARD -n -v
|
||||||
|
|
||||||
|
echo "==== Master 网关配置完成 ===="
|
||||||
|
echo "Master 节点现在可以作为内网节点的网关使用"
|
||||||
26
docs/kubernetes/setup-node1.sh
Normal file
26
docs/kubernetes/setup-node1.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "==== 配置 Node1 (172.17.0.43) 网络路由 ===="
|
||||||
|
|
||||||
|
echo "==== 当前状态 ===="
|
||||||
|
echo "当前主机名: $(hostname)"
|
||||||
|
echo "当前 IP: $(ip route get 1 | awk '{print $7; exit}')"
|
||||||
|
|
||||||
|
# 配置网络路由 - 通过 master 访问外网
|
||||||
|
echo "配置网络路由..."
|
||||||
|
# 删除默认网关(如果存在)
|
||||||
|
sudo ip route del default 2>/dev/null || true
|
||||||
|
|
||||||
|
# 添加默认网关指向 master
|
||||||
|
sudo ip route add default via 172.17.0.15
|
||||||
|
|
||||||
|
echo "==== 验证网络配置 ===="
|
||||||
|
echo "当前路由表:"
|
||||||
|
ip route show
|
||||||
|
|
||||||
|
echo "测试网络连通性:"
|
||||||
|
ping -c 2 172.17.0.15 && echo "✓ 可以访问 master" || echo "✗ 无法访问 master"
|
||||||
|
ping -c 2 8.8.8.8 && echo "✓ 可以访问外网" || echo "✗ 无法访问外网"
|
||||||
|
|
||||||
|
echo "==== Node1 网络路由配置完成 ===="
|
||||||
26
docs/kubernetes/setup-node2.sh
Normal file
26
docs/kubernetes/setup-node2.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "==== 配置 Node2 (172.17.0.34) 网络路由 ===="
|
||||||
|
|
||||||
|
echo "==== 当前状态 ===="
|
||||||
|
echo "当前主机名: $(hostname)"
|
||||||
|
echo "当前 IP: $(ip route get 1 | awk '{print $7; exit}')"
|
||||||
|
|
||||||
|
# 配置网络路由 - 通过 master 访问外网
|
||||||
|
echo "配置网络路由..."
|
||||||
|
# 删除默认网关(如果存在)
|
||||||
|
sudo ip route del default 2>/dev/null || true
|
||||||
|
|
||||||
|
# 添加默认网关指向 master
|
||||||
|
sudo ip route add default via 172.17.0.15
|
||||||
|
|
||||||
|
echo "==== 验证网络配置 ===="
|
||||||
|
echo "当前路由表:"
|
||||||
|
ip route show
|
||||||
|
|
||||||
|
echo "测试网络连通性:"
|
||||||
|
ping -c 2 172.17.0.15 && echo "✓ 可以访问 master" || echo "✗ 无法访问 master"
|
||||||
|
ping -c 2 8.8.8.8 && echo "✓ 可以访问外网" || echo "✗ 无法访问外网"
|
||||||
|
|
||||||
|
echo "==== Node2 网络路由配置完成 ===="
|
||||||
@@ -667,7 +667,6 @@ func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, o
|
|||||||
u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
|
u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
|
||||||
u.Visibility = setting.Service.DefaultUserVisibilityMode
|
u.Visibility = setting.Service.DefaultUserVisibilityMode
|
||||||
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
|
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
|
||||||
u.AllowCreateDevcontainer = setting.Service.DefaultAllowCreateDevcontainer
|
|
||||||
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
|
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
|
||||||
u.MaxRepoCreation = -1
|
u.MaxRepoCreation = -1
|
||||||
u.Theme = setting.UI.DefaultTheme
|
u.Theme = setting.UI.DefaultTheme
|
||||||
|
|||||||
@@ -89,22 +89,24 @@ func GetContainerStatus(cli *client.Client, containerID string) (string, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
state := containerInfo.State
|
state := containerInfo.State
|
||||||
return state.Status, nil
|
return state.Status, nil
|
||||||
}
|
}
|
||||||
func PushImage(dockerHost string, username string, password string, registryUrl string, imageRef string) error {
|
func PushImage(dockerHost string, username string, password string, registryUrl string, imageRef string) error {
|
||||||
script := "docker " + "-H " + dockerHost + " login -u " + username + " -p " + password + " " + registryUrl + " "
|
script := "docker " + "-H " + dockerHost + " login -u " + username + " -p " + password + " " + registryUrl + " "
|
||||||
cmd := exec.Command("sh", "-c", script)
|
cmd := exec.Command("sh", "-c", script)
|
||||||
output, err := cmd.CombinedOutput()
|
_, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s \n 镜像登录失败: %s", string(output), err.Error())
|
return err
|
||||||
}
|
}
|
||||||
// 推送到仓库
|
// 推送到仓库
|
||||||
script = "docker " + "-H " + dockerHost + " push " + imageRef
|
script = "docker " + "-H " + dockerHost + " push " + imageRef
|
||||||
cmd = exec.Command("sh", "-c", script)
|
cmd = exec.Command("sh", "-c", script)
|
||||||
output, err = cmd.CombinedOutput()
|
_, err = cmd.CombinedOutput()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s \n 镜像推送失败: %s", string(output), err.Error())
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ var Service = struct {
|
|||||||
McaptchaURL string
|
McaptchaURL string
|
||||||
DefaultKeepEmailPrivate bool
|
DefaultKeepEmailPrivate bool
|
||||||
DefaultAllowCreateOrganization bool
|
DefaultAllowCreateOrganization bool
|
||||||
DefaultAllowCreateDevcontainer bool
|
|
||||||
DefaultUserIsRestricted bool
|
DefaultUserIsRestricted bool
|
||||||
EnableTimetracking bool
|
EnableTimetracking bool
|
||||||
DefaultEnableTimetracking bool
|
DefaultEnableTimetracking bool
|
||||||
@@ -206,7 +205,6 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
|||||||
Service.McaptchaSitekey = sec.Key("MCAPTCHA_SITEKEY").MustString("")
|
Service.McaptchaSitekey = sec.Key("MCAPTCHA_SITEKEY").MustString("")
|
||||||
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
|
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
|
||||||
Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
|
Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
|
||||||
Service.DefaultAllowCreateDevcontainer = sec.Key("DEFAULT_ALLOW_CREATE_DEVCONTAINER").MustBool(true)
|
|
||||||
Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)
|
Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)
|
||||||
Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)
|
Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)
|
||||||
if Service.EnableTimetracking {
|
if Service.EnableTimetracking {
|
||||||
|
|||||||
@@ -362,9 +362,7 @@ invalid_log_root_path = The log path is invalid: %v
|
|||||||
default_keep_email_private = Hide Email Addresses by Default
|
default_keep_email_private = Hide Email Addresses by Default
|
||||||
default_keep_email_private_popup = Hide email addresses of new user accounts by default.
|
default_keep_email_private_popup = Hide email addresses of new user accounts by default.
|
||||||
default_allow_create_organization = Allow Creation of Organizations by Default
|
default_allow_create_organization = Allow Creation of Organizations by Default
|
||||||
default_allow_create_devcontainer = Allow Creation of DevContainers by Default
|
|
||||||
default_allow_create_organization_popup = Allow new user accounts to create organizations by default.
|
default_allow_create_organization_popup = Allow new user accounts to create organizations by default.
|
||||||
default_allow_create_devcontainer_popup = Allow new user accounts to create devcontainers by default.
|
|
||||||
default_enable_timetracking = Enable Time Tracking by Default
|
default_enable_timetracking = Enable Time Tracking by Default
|
||||||
default_enable_timetracking_popup = Enable time tracking for new repositories by default.
|
default_enable_timetracking_popup = Enable time tracking for new repositories by default.
|
||||||
no_reply_address = Hidden Email Domain
|
no_reply_address = Hidden Email Domain
|
||||||
@@ -3422,7 +3420,6 @@ config.active_code_lives = Active Code Lives
|
|||||||
config.reset_password_code_lives = Recover Account Code Expiry Time
|
config.reset_password_code_lives = Recover Account Code Expiry Time
|
||||||
config.default_keep_email_private = Hide Email Addresses by Default
|
config.default_keep_email_private = Hide Email Addresses by Default
|
||||||
config.default_allow_create_organization = Allow Creation of Organizations by Default
|
config.default_allow_create_organization = Allow Creation of Organizations by Default
|
||||||
config.default_allow_create_devcontainer = Allow Creation of Dev Containers by Default
|
|
||||||
config.enable_timetracking = Enable Time Tracking
|
config.enable_timetracking = Enable Time Tracking
|
||||||
config.default_enable_timetracking = Enable Time Tracking by Default
|
config.default_enable_timetracking = Enable Time Tracking by Default
|
||||||
config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time
|
config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time
|
||||||
|
|||||||
@@ -357,9 +357,7 @@ invalid_log_root_path=日志路径无效: %v
|
|||||||
default_keep_email_private=默认情况下隐藏邮箱地址
|
default_keep_email_private=默认情况下隐藏邮箱地址
|
||||||
default_keep_email_private_popup=默认情况下,隐藏新用户帐户的邮箱地址。
|
default_keep_email_private_popup=默认情况下,隐藏新用户帐户的邮箱地址。
|
||||||
default_allow_create_organization=默认情况下允许创建组织
|
default_allow_create_organization=默认情况下允许创建组织
|
||||||
default_allow_create_devcontainer=默认情况下允许创建容器
|
|
||||||
default_allow_create_organization_popup=默认情况下, 允许新用户帐户创建组织。
|
default_allow_create_organization_popup=默认情况下, 允许新用户帐户创建组织。
|
||||||
default_allow_create_devcontainer_popup=默认情况下, 允许新用户帐户创建容器。
|
|
||||||
default_enable_timetracking=默认情况下启用时间跟踪
|
default_enable_timetracking=默认情况下启用时间跟踪
|
||||||
default_enable_timetracking_popup=默认情况下启用新仓库的时间跟踪。
|
default_enable_timetracking_popup=默认情况下启用新仓库的时间跟踪。
|
||||||
no_reply_address=隐藏邮件域
|
no_reply_address=隐藏邮件域
|
||||||
@@ -3410,7 +3408,6 @@ config.active_code_lives=激活用户链接有效期
|
|||||||
config.reset_password_code_lives=恢复账户验证码过期时间
|
config.reset_password_code_lives=恢复账户验证码过期时间
|
||||||
config.default_keep_email_private=默认隐藏邮箱地址
|
config.default_keep_email_private=默认隐藏邮箱地址
|
||||||
config.default_allow_create_organization=默认情况下允许创建组织
|
config.default_allow_create_organization=默认情况下允许创建组织
|
||||||
config.default_allow_create_devcontainer=默认情况下允许创建 DevContainer
|
|
||||||
config.enable_timetracking=启用时间跟踪
|
config.enable_timetracking=启用时间跟踪
|
||||||
config.default_enable_timetracking=默认情况下启用时间跟踪
|
config.default_enable_timetracking=默认情况下启用时间跟踪
|
||||||
config.default_allow_only_contributors_to_track_time=仅允许成员跟踪时间
|
config.default_allow_only_contributors_to_track_time=仅允许成员跟踪时间
|
||||||
|
|||||||
@@ -86,12 +86,12 @@ function install {
|
|||||||
sudo docker pull devstar.cn/devstar/$IMAGE_NAME:$VERSION
|
sudo docker pull devstar.cn/devstar/$IMAGE_NAME:$VERSION
|
||||||
IMAGE_REGISTRY_USER=devstar.cn/devstar
|
IMAGE_REGISTRY_USER=devstar.cn/devstar
|
||||||
fi
|
fi
|
||||||
if sudo docker pull mengning997/webterminal:latest; then
|
if sudo docker pull devstar.cn/devstar/webterminal:latest; then
|
||||||
sudo docker tag mengning997/webterminal:latest devstar.cn/devstar/webterminal:latest
|
success "Successfully pulled devstar.cn/devstar/webterminal:latest"
|
||||||
success "Successfully pulled mengning997/webterminal:latest renamed to devstar.cn/devstar/webterminal:latest"
|
|
||||||
else
|
else
|
||||||
sudo docker pull devstar.cn/devstar/webterminal:latest
|
sudo docker pull mengning997/webterminal:latest
|
||||||
success "Successfully pulled devstar.cn/devstar/webterminal:latest"
|
success "Successfully pulled mengning997/webterminal:latest renamed to devstar.cn/devstar/webterminal:latest"
|
||||||
|
sudo docker tag mengning997/webterminal:latest devstar.cn/devstar/webterminal:latest
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,9 +138,6 @@ function stop {
|
|||||||
if [ $(docker ps -a --filter "name=^/devstar-studio$" -q | wc -l) -gt 0 ]; then
|
if [ $(docker ps -a --filter "name=^/devstar-studio$" -q | wc -l) -gt 0 ]; then
|
||||||
sudo docker stop devstar-studio && sudo docker rm -f devstar-studio
|
sudo docker stop devstar-studio && sudo docker rm -f devstar-studio
|
||||||
fi
|
fi
|
||||||
if [ $(docker ps -a --filter "name=^/webterminal-" -q | wc -l) -gt 0 ]; then
|
|
||||||
sudo docker stop $(docker ps -a --filter "name=^/webterminal-" -q) && sudo docker rm -f $(docker ps -a --filter "name=^/webterminal-" -q)
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to logs
|
# Function to logs
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ func Install(ctx *context.Context) {
|
|||||||
form.RequireSignInView = setting.Service.RequireSignInViewStrict
|
form.RequireSignInView = setting.Service.RequireSignInViewStrict
|
||||||
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
|
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
|
||||||
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
|
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
|
||||||
form.DefaultAllowCreateDevcontainer = setting.Service.DefaultAllowCreateDevcontainer
|
|
||||||
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
|
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
|
||||||
form.NoReplyAddress = setting.Service.NoReplyAddress
|
form.NoReplyAddress = setting.Service.NoReplyAddress
|
||||||
form.PasswordAlgorithm = hash.ConfigHashAlgorithm(setting.PasswordHashAlgo)
|
form.PasswordAlgorithm = hash.ConfigHashAlgorithm(setting.PasswordHashAlgo)
|
||||||
@@ -491,7 +490,6 @@ func SubmitInstall(ctx *context.Context) {
|
|||||||
cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(strconv.FormatBool(form.RequireSignInView))
|
cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(strconv.FormatBool(form.RequireSignInView))
|
||||||
cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(strconv.FormatBool(form.DefaultKeepEmailPrivate))
|
cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(strconv.FormatBool(form.DefaultKeepEmailPrivate))
|
||||||
cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(strconv.FormatBool(form.DefaultAllowCreateOrganization))
|
cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(strconv.FormatBool(form.DefaultAllowCreateOrganization))
|
||||||
cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_DEVCONTAINER").SetValue(strconv.FormatBool(form.DefaultAllowCreateDevcontainer))
|
|
||||||
cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(strconv.FormatBool(form.DefaultEnableTimetracking))
|
cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(strconv.FormatBool(form.DefaultEnableTimetracking))
|
||||||
cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(form.NoReplyAddress)
|
cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(form.NoReplyAddress)
|
||||||
cfg.Section("cron.update_checker").Key("ENABLED").SetValue(strconv.FormatBool(form.EnableUpdateChecker))
|
cfg.Section("cron.update_checker").Key("ENABLED").SetValue(strconv.FormatBool(form.EnableUpdateChecker))
|
||||||
|
|||||||
@@ -55,22 +55,21 @@ func GetDevContainerDetails(ctx *context.Context) {
|
|||||||
ctx.Data["ValidateDevContainerConfiguration"] = false
|
ctx.Data["ValidateDevContainerConfiguration"] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["HasDevContainerDockerfile"], ctx.Data["DockerfilePath"], err = devcontainer_service.HasDevContainerDockerFile(ctx, ctx.Repo)
|
ctx.Data["HasDevContainerDockerfile"], err = devcontainer_service.HasDevContainerDockerFile(ctx, ctx.Repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info(err.Error())
|
log.Info(err.Error())
|
||||||
ctx.Flash.Error(err.Error(), true)
|
ctx.Flash.Error(err.Error(), true)
|
||||||
}
|
}
|
||||||
if ctx.Data["HasDevContainer"] == true {
|
if ctx.Data["HasDevContainer"] == true {
|
||||||
if ctx.Data["HasDevContainerConfiguration"] == true {
|
configurationString, _ := devcontainer_service.GetDevcontainerConfigurationString(ctx, ctx.Repo.Repository)
|
||||||
configurationString, _ := devcontainer_service.GetDevcontainerConfigurationString(ctx, ctx.Repo.Repository)
|
configurationModel, _ := devcontainer_service.UnmarshalDevcontainerConfigContent(configurationString)
|
||||||
configurationModel, _ := devcontainer_service.UnmarshalDevcontainerConfigContent(configurationString)
|
imageName := configurationModel.Image
|
||||||
imageName := configurationModel.Image
|
registry, namespace, repo, tag := devcontainer_service.ParseImageName(imageName)
|
||||||
registry, namespace, repo, tag := devcontainer_service.ParseImageName(imageName)
|
log.Info("%v %v", repo, tag)
|
||||||
log.Info("%v %v", repo, tag)
|
ctx.Data["RepositoryAddress"] = registry
|
||||||
ctx.Data["RepositoryAddress"] = registry
|
ctx.Data["RepositoryUsername"] = namespace
|
||||||
ctx.Data["RepositoryUsername"] = namespace
|
ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest"
|
||||||
ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest"
|
|
||||||
}
|
|
||||||
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
||||||
// 获取WebSSH服务端口
|
// 获取WebSSH服务端口
|
||||||
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
|
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
|
||||||
@@ -112,6 +111,7 @@ func GetDevContainerDetails(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer"))
|
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer"))
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
rootPort, err := devcontainer_service.GetPortFromURL(cfg.Section("server").Key("ROOT_URL").Value())
|
rootPort, err := devcontainer_service.GetPortFromURL(cfg.Section("server").Key("ROOT_URL").Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Flash.Error(err.Error(), true)
|
ctx.Flash.Error(err.Error(), true)
|
||||||
@@ -136,6 +136,7 @@ func GetDevContainerDetails(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
ctx.Data["WebSSHUrl"] = webTerminalURL + "?type=docker&" + terminalParams
|
ctx.Data["WebSSHUrl"] = webTerminalURL + "?type=docker&" + terminalParams
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
terminalURL, err := devcontainer_service.Get_IDE_TerminalURL(ctx, ctx.Doer, ctx.Repo)
|
terminalURL, err := devcontainer_service.Get_IDE_TerminalURL(ctx, ctx.Doer, ctx.Repo)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -144,6 +145,7 @@ func GetDevContainerDetails(ctx *context.Context) {
|
|||||||
ctx.Data["WindsurfUrl"] = "windsurf" + terminalURL
|
ctx.Data["WindsurfUrl"] = "windsurf" + terminalURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 携带数据渲染页面,返回
|
// 3. 携带数据渲染页面,返回
|
||||||
ctx.Data["Title"] = ctx.Locale.Tr("repo.dev_container")
|
ctx.Data["Title"] = ctx.Locale.Tr("repo.dev_container")
|
||||||
ctx.Data["PageIsDevContainer"] = true
|
ctx.Data["PageIsDevContainer"] = true
|
||||||
@@ -298,7 +300,7 @@ func UpdateDevContainer(ctx *context.Context) {
|
|||||||
ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()})
|
ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = devcontainer_service.UpdateDevContainer(ctx, ctx.Doer, ctx.Repo, &updateInfo)
|
err = devcontainer_service.UpdateDevContainer(ctx, ctx.Doer, ctx.Repo.Repository, &updateInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()})
|
ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()})
|
||||||
return
|
return
|
||||||
@@ -316,43 +318,18 @@ func GetTerminalCommand(ctx *context.Context) {
|
|||||||
log.Info(err.Error())
|
log.Info(err.Error())
|
||||||
status = "error"
|
status = "error"
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusOK, map[string]string{"command": cmd, "status": status, "workdir": "/workspace/" + ctx.Repo.Repository.Name})
|
|
||||||
|
ctx.JSON(http.StatusOK, map[string]string{"command": cmd, "status": status})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDevContainerOutput(ctx *context.Context) {
|
func GetDevContainerOutput(ctx *context.Context) {
|
||||||
// 设置 CORS 响应头
|
// 设置 CORS 响应头
|
||||||
ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*")
|
ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
ctx.Resp.Header().Set("Access-Control-Allow-Methods", "*")
|
ctx.Resp.Header().Set("Access-Control-Allow-Methods", "*")
|
||||||
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "*")
|
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "*")
|
||||||
query := ctx.Req.URL.Query()
|
output, err := devcontainer_service.GetDevContainerOutput(ctx, ctx.Doer, ctx.Repo.Repository)
|
||||||
output, err := devcontainer_service.GetDevContainerOutput(ctx, query.Get("user"), ctx.Repo.Repository)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info(err.Error())
|
log.Info(err.Error())
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusOK, map[string]string{"output": output})
|
ctx.JSON(http.StatusOK, output)
|
||||||
}
|
|
||||||
func SaveDevContainerOutput(ctx *context.Context) {
|
|
||||||
// 设置 CORS 响应头
|
|
||||||
ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
ctx.Resp.Header().Set("Access-Control-Allow-Methods", "*")
|
|
||||||
ctx.Resp.Header().Set("Access-Control-Allow-Headers", "*")
|
|
||||||
// 处理 OPTIONS 预检请求
|
|
||||||
if ctx.Req.Method == "OPTIONS" {
|
|
||||||
ctx.JSON(http.StatusOK, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
query := ctx.Req.URL.Query()
|
|
||||||
|
|
||||||
// 从请求体中读取输出内容
|
|
||||||
body, err := io.ReadAll(ctx.Req.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to read request body: %v", err)
|
|
||||||
ctx.JSON(http.StatusBadRequest, map[string]string{"error": "Failed to read request body"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = devcontainer_service.SaveDevContainerOutput(ctx, query.Get("user"), ctx.Repo.Repository, string(body))
|
|
||||||
if err != nil {
|
|
||||||
log.Info(err.Error())
|
|
||||||
}
|
|
||||||
ctx.JSON(http.StatusOK, "")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/models/issues"
|
"code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
@@ -26,7 +25,6 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/context/upload"
|
"code.gitea.io/gitea/services/context/upload"
|
||||||
devcontainer_service "code.gitea.io/gitea/services/devcontainer"
|
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
files_service "code.gitea.io/gitea/services/repository/files"
|
files_service "code.gitea.io/gitea/services/repository/files"
|
||||||
)
|
)
|
||||||
@@ -413,23 +411,6 @@ func DeleteFilePost(ctx *context.Context) {
|
|||||||
editorHandleFileOperationError(ctx, parsed.NewBranchName, err)
|
editorHandleFileOperationError(ctx, parsed.NewBranchName, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("File deleted: %s", treePath)
|
|
||||||
if treePath == `.devcontainer/devcontainer.json` {
|
|
||||||
var userIds []int64
|
|
||||||
err = db.GetEngine(ctx).
|
|
||||||
Table("devcontainer").
|
|
||||||
Select("user_id").
|
|
||||||
Where("repo_id = ?", ctx.Repo.Repository.ID).
|
|
||||||
Find(&userIds)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetEngine", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, userId := range userIds {
|
|
||||||
devcontainer_service.DeleteDevContainer(ctx, userId, ctx.Repo.Repository.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
|
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
|
||||||
redirectTreePath := getClosestParentWithFiles(ctx.Repo.GitRepo, parsed.NewBranchName, treePath)
|
redirectTreePath := getClosestParentWithFiles(ctx.Repo.GitRepo, parsed.NewBranchName, treePath)
|
||||||
|
|||||||
@@ -1434,14 +1434,13 @@ func registerWebRoutes(m *web.Router) {
|
|||||||
m.Get("/status", devcontainer_web.GetDevContainerStatus)
|
m.Get("/status", devcontainer_web.GetDevContainerStatus)
|
||||||
m.Get("/command", devcontainer_web.GetTerminalCommand)
|
m.Get("/command", devcontainer_web.GetTerminalCommand)
|
||||||
m.Get("/output", devcontainer_web.GetDevContainerOutput)
|
m.Get("/output", devcontainer_web.GetDevContainerOutput)
|
||||||
m.Methods("POST, OPTIONS", "/output", devcontainer_web.SaveDevContainerOutput)
|
|
||||||
},
|
},
|
||||||
// 解析仓库信息
|
// 解析仓库信息
|
||||||
// 具有code读取权限
|
// 具有code读取权限
|
||||||
context.RepoAssignment, reqUnitCodeReader,
|
context.RepoAssignment, reqUnitCodeReader,
|
||||||
)
|
)
|
||||||
m.Get("/devstar-home", devcontainer_web.VscodeHome) // 旧地址,保留兼容性
|
m.Get("/devstar-home", devcontainer_web.VscodeHome) // 旧地址,保留兼容性
|
||||||
m.Get("/vscode-home", devcontainer_web.VscodeHome)
|
m.Get("/vscode-home", devcontainer_web.VscodeHome)
|
||||||
m.Group("/api/devcontainer", func() {
|
m.Group("/api/devcontainer", func() {
|
||||||
// 获取 某用户在某仓库中的 DevContainer 细节(包括SSH连接信息),默认不会等待 (wait = false)
|
// 获取 某用户在某仓库中的 DevContainer 细节(包括SSH连接信息),默认不会等待 (wait = false)
|
||||||
// 请求方式: GET /api/devcontainer?repoId=${repoId}&wait=true // 无需传入 userId,直接从 token 中提取
|
// 请求方式: GET /api/devcontainer?repoId=${repoId}&wait=true // 无需传入 userId,直接从 token 中提取
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -68,21 +70,21 @@ func HasDevContainerConfiguration(ctx context.Context, repo *gitea_context.Repos
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func HasDevContainerDockerFile(ctx context.Context, repo *gitea_context.Repository) (bool, string, error) {
|
func HasDevContainerDockerFile(ctx context.Context, repo *gitea_context.Repository) (bool, error) {
|
||||||
_, err := FileExists(".devcontainer/devcontainer.json", repo)
|
_, err := FileExists(".devcontainer/devcontainer.json", repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsErrNotExist(err) {
|
if git.IsErrNotExist(err) {
|
||||||
return false, "", nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return false, "", err
|
return false, err
|
||||||
}
|
}
|
||||||
configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository)
|
configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err
|
return false, err
|
||||||
}
|
}
|
||||||
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
|
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", err
|
return false, err
|
||||||
}
|
}
|
||||||
// 执行验证
|
// 执行验证
|
||||||
if errs := configurationModel.Validate(); len(errs) > 0 {
|
if errs := configurationModel.Validate(); len(errs) > 0 {
|
||||||
@@ -90,34 +92,20 @@ func HasDevContainerDockerFile(ctx context.Context, repo *gitea_context.Reposito
|
|||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
fmt.Printf(" - %s\n", err.Error())
|
fmt.Printf(" - %s\n", err.Error())
|
||||||
}
|
}
|
||||||
return false, "", fmt.Errorf("配置格式错误")
|
return false, fmt.Errorf("配置格式错误")
|
||||||
} else {
|
} else {
|
||||||
log.Info("%v", configurationModel)
|
log.Info("%v", configurationModel)
|
||||||
if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" {
|
if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" {
|
||||||
_, err := FileExists(".devcontainer/Dockerfile", repo)
|
return false, nil
|
||||||
if err != nil {
|
|
||||||
if git.IsErrNotExist(err) {
|
|
||||||
return false, "", nil
|
|
||||||
}
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
return true, ".devcontainer/Dockerfile", nil
|
|
||||||
}
|
}
|
||||||
_, err := FileExists(".devcontainer/"+configurationModel.Build.Dockerfile, repo)
|
_, err := FileExists(".devcontainer/"+configurationModel.Build.Dockerfile, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsErrNotExist(err) {
|
if git.IsErrNotExist(err) {
|
||||||
_, err := FileExists(".devcontainer/Dockerfile", repo)
|
return false, nil
|
||||||
if err != nil {
|
|
||||||
if git.IsErrNotExist(err) {
|
|
||||||
return false, "", nil
|
|
||||||
}
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
return true, ".devcontainer/Dockerfile", nil
|
|
||||||
}
|
}
|
||||||
return false, "", err
|
return false, err
|
||||||
}
|
}
|
||||||
return true, ".devcontainer/" + configurationModel.Build.Dockerfile, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func CreateDevcontainerConfiguration(repo *repo.Repository, doer *user.User) error {
|
func CreateDevcontainerConfiguration(repo *repo.Repository, doer *user.User) error {
|
||||||
@@ -447,7 +435,7 @@ func StopDevContainer(ctx context.Context, userID, repoID int64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateDevContainer(ctx context.Context, doer *user.User, repo *gitea_context.Repository, updateInfo *UpdateInfo) error {
|
func UpdateDevContainer(ctx context.Context, doer *user.User, repo *repo.Repository, updateInfo *UpdateInfo) error {
|
||||||
dbEngine := db.GetEngine(ctx)
|
dbEngine := db.GetEngine(ctx)
|
||||||
var devContainerInfo devcontainer_models.Devcontainer
|
var devContainerInfo devcontainer_models.Devcontainer
|
||||||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||||||
@@ -457,24 +445,25 @@ func UpdateDevContainer(ctx context.Context, doer *user.User, repo *gitea_contex
|
|||||||
_, err = dbEngine.
|
_, err = dbEngine.
|
||||||
Table("devcontainer").
|
Table("devcontainer").
|
||||||
Select("*").
|
Select("*").
|
||||||
Where("user_id = ? AND repo_id = ?", doer.ID, repo.Repository.ID).
|
Where("user_id = ? AND repo_id = ?", doer.ID, repo.ID).
|
||||||
Get(&devContainerInfo)
|
Get(&devContainerInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = dbEngine.Table("devcontainer").
|
_, err = dbEngine.Table("devcontainer").
|
||||||
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.Repository.ID).
|
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.ID).
|
||||||
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 5})
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 5})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
otherCtx := context.Background()
|
otherCtx := context.Background()
|
||||||
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
||||||
//k8s的逻辑
|
//k8s的逻辑
|
||||||
} else {
|
} else {
|
||||||
updateErr := UpdateDevContainerByDocker(otherCtx, &devContainerInfo, updateInfo, repo, doer)
|
updateErr := UpdateDevContainerByDocker(otherCtx, &devContainerInfo, updateInfo, repo, doer)
|
||||||
_, err = dbEngine.Table("devcontainer").
|
_, err = dbEngine.Table("devcontainer").
|
||||||
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.Repository.ID).
|
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.ID).
|
||||||
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4})
|
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -545,72 +534,58 @@ func GetTerminalCommand(ctx context.Context, userID string, repo *repo.Repositor
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
//正在创建容器,创建容器成功,则状态转移
|
//正在创建容器,创建容器成功,则状态转移
|
||||||
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
||||||
//k8s的逻辑
|
//k8s的逻辑
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
exist, _, err := ContainerExists(ctx, devContainerInfo.Name)
|
status, err := GetDevContainerStatusFromDocker(ctx, devContainerInfo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
if !exist {
|
if status == "created" {
|
||||||
_, err = dbEngine.Table("devcontainer_output").
|
//添加脚本文件
|
||||||
Select("command").
|
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
||||||
Where("user_id = ? AND repo_id = ? AND list_id = ?", userID, repo.ID, realTimeStatus).
|
} else {
|
||||||
Get(&cmd)
|
userNum, err := strconv.ParseInt(userID, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
|
||||||
} else {
|
|
||||||
status, err := GetDevContainerStatusFromDocker(ctx, devContainerInfo.Name)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
if status == "created" {
|
|
||||||
//添加脚本文件
|
|
||||||
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
|
|
||||||
} else {
|
|
||||||
userNum, err := strconv.ParseInt(userID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
var scriptContent string
|
|
||||||
scriptContent, err = GetCommandContent(ctx, userNum, repo)
|
|
||||||
log.Info("command: %s", scriptContent)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
// 创建 tar 归档文件
|
|
||||||
var buf bytes.Buffer
|
|
||||||
tw := tar.NewWriter(&buf)
|
|
||||||
defer tw.Close()
|
|
||||||
// 添加文件到 tar 归档
|
|
||||||
AddFileToTar(tw, "webTerminal.sh", string(scriptContent), 0777)
|
|
||||||
// 创建 Docker 客户端
|
|
||||||
cli, err := docker_module.CreateDockerClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
// 获取容器 ID
|
|
||||||
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
err = cli.CopyToContainer(ctx, containerID, "/home", bytes.NewReader(buf.Bytes()), types.CopyToContainerOptions{})
|
|
||||||
if err != nil {
|
|
||||||
log.Info("%v", err)
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
realTimeStatus = 3
|
var scriptContent string
|
||||||
|
scriptContent, err = GetCommandContent(ctx, userNum, repo)
|
||||||
|
log.Info("command: %s", scriptContent)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
// 创建 tar 归档文件
|
||||||
|
var buf bytes.Buffer
|
||||||
|
tw := tar.NewWriter(&buf)
|
||||||
|
defer tw.Close()
|
||||||
|
|
||||||
|
// 添加文件到 tar 归档
|
||||||
|
AddFileToTar(tw, "webTerminal.sh", string(scriptContent), 0777)
|
||||||
|
// 创建 Docker 客户端
|
||||||
|
cli, err := docker_module.CreateDockerClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
// 获取容器 ID
|
||||||
|
containerID, err := docker_module.GetContainerID(cli, devContainerInfo.Name)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
err = cli.CopyToContainer(ctx, containerID, "/home", bytes.NewReader(buf.Bytes()), types.CopyToContainerOptions{})
|
||||||
|
if err != nil {
|
||||||
|
log.Info("%v", err)
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
realTimeStatus = 3
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 3:
|
case 3:
|
||||||
@@ -639,27 +614,6 @@ func GetTerminalCommand(ctx context.Context, userID string, repo *repo.Repositor
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
configurationString, err := GetDevcontainerConfigurationString(ctx, repo)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
postAttachCommand := strings.TrimSpace(strings.Join(configurationModel.ParseCommand(configurationModel.PostAttachCommand), "\n"))
|
|
||||||
if _, ok := configurationModel.PostAttachCommand.(map[string]interface{}); ok {
|
|
||||||
// 是 map[string]interface{} 类型
|
|
||||||
cmdObj := configurationModel.PostAttachCommand.(map[string]interface{})
|
|
||||||
if pathValue, hasPath := cmdObj["path"]; hasPath {
|
|
||||||
fileCommand, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+pathValue.(string))
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
postAttachCommand += "\n" + fileCommand
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd += postAttachCommand
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -682,59 +636,67 @@ func GetTerminalCommand(ctx context.Context, userID string, repo *repo.Repositor
|
|||||||
}
|
}
|
||||||
return cmd, fmt.Sprintf("%d", realTimeStatus), nil
|
return cmd, fmt.Sprintf("%d", realTimeStatus), nil
|
||||||
}
|
}
|
||||||
func GetDevContainerOutput(ctx context.Context, user_id string, repo *repo.Repository) (string, error) {
|
func GetDevContainerOutput(ctx context.Context, doer *user.User, repo *repo.Repository) (OutputResponse, error) {
|
||||||
var devContainerOutput string
|
var devContainerOutput []devcontainer_models.DevcontainerOutput
|
||||||
dbEngine := db.GetEngine(ctx)
|
dbEngine := db.GetEngine(ctx)
|
||||||
|
resp := OutputResponse{}
|
||||||
|
var status string
|
||||||
|
var containerName string
|
||||||
|
_, err := dbEngine.
|
||||||
|
Table("devcontainer").
|
||||||
|
Select("devcontainer_status, name").
|
||||||
|
Where("user_id = ? AND repo_id = ?", doer.ID, repo.ID).
|
||||||
|
Get(&status, &containerName)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
_, err := dbEngine.Table("devcontainer_output").
|
err = dbEngine.Table("devcontainer_output").
|
||||||
Select("output").
|
Where("user_id = ? AND repo_id = ?", doer.ID, repo.ID).
|
||||||
Where("user_id = ? AND repo_id = ? AND list_id = ?", user_id, repo.ID, 4).
|
Find(&devContainerOutput)
|
||||||
Get(&devContainerOutput)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return resp, err
|
||||||
}
|
}
|
||||||
if devContainerOutput != "" {
|
|
||||||
_, err = dbEngine.Table("devcontainer_output").
|
if len(devContainerOutput) > 0 {
|
||||||
Where("user_id = ? AND repo_id = ? AND list_id = ?", user_id, repo.ID, 4).
|
|
||||||
Update(map[string]interface{}{
|
resp.CurrentJob.Title = repo.Name + " Devcontainer Info"
|
||||||
"output": "",
|
resp.CurrentJob.Detail = status
|
||||||
|
if status == "4" {
|
||||||
|
// 获取WebSSH服务端口
|
||||||
|
webTerminalURL, err := GetWebTerminalURL(ctx, doer.ID, repo.ID)
|
||||||
|
if err == nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
// 解析URL
|
||||||
|
u, err := url.Parse(webTerminalURL)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
// 分离主机和端口
|
||||||
|
terminalHost, terminalPort, err := net.SplitHostPort(u.Host)
|
||||||
|
resp.CurrentJob.IP = terminalHost
|
||||||
|
resp.CurrentJob.Port = terminalPort
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, item := range devContainerOutput {
|
||||||
|
logLines := []ViewStepLogLine{}
|
||||||
|
logLines = append(logLines, ViewStepLogLine{
|
||||||
|
Index: 1,
|
||||||
|
Message: item.Output,
|
||||||
})
|
})
|
||||||
if err != nil {
|
resp.CurrentJob.Steps = append(resp.CurrentJob.Steps, &ViewJobStep{
|
||||||
return "", err
|
Summary: item.Command,
|
||||||
|
Status: item.Status,
|
||||||
|
Logs: logLines,
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return resp, nil
|
||||||
return devContainerOutput, nil
|
|
||||||
}
|
|
||||||
func SaveDevContainerOutput(ctx context.Context, user_id string, repo *repo.Repository, newoutput string) error {
|
|
||||||
var devContainerOutput string
|
|
||||||
var finalOutput string
|
|
||||||
dbEngine := db.GetEngine(ctx)
|
|
||||||
|
|
||||||
// 从数据库中获取现有的输出内容
|
|
||||||
_, err := dbEngine.Table("devcontainer_output").
|
|
||||||
Select("output").
|
|
||||||
Where("user_id = ? AND repo_id = ? AND list_id = ?", user_id, repo.ID, 4).
|
|
||||||
Get(&devContainerOutput)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
devContainerOutput = strings.TrimSuffix(devContainerOutput, "\r\n")
|
|
||||||
if newoutput == "\b \b" {
|
|
||||||
finalOutput = devContainerOutput[:len(devContainerOutput)-1]
|
|
||||||
} else {
|
|
||||||
finalOutput = devContainerOutput + newoutput
|
|
||||||
}
|
|
||||||
_, err = dbEngine.Table("devcontainer_output").
|
|
||||||
Where("user_id = ? AND repo_id = ? AND list_id = ?", user_id, repo.ID, 4).
|
|
||||||
Update(map[string]interface{}{
|
|
||||||
"output": finalOutput + "\r\n",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
func GetMappedPort(ctx context.Context, containerName string, port string) (uint16, error) {
|
func GetMappedPort(ctx context.Context, containerName string, port string) (uint16, error) {
|
||||||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||||||
@@ -975,6 +937,7 @@ func GetCommandContent(ctx context.Context, userId int64, repo *repo.Repository)
|
|||||||
script = append(script, v)
|
script = append(script, v)
|
||||||
}
|
}
|
||||||
scriptCommand := strings.TrimSpace(strings.Join(script, "\n"))
|
scriptCommand := strings.TrimSpace(strings.Join(script, "\n"))
|
||||||
|
|
||||||
userCommand := scriptCommand + "\n" + onCreateCommand + "\n" + updateCommand + "\n" + postCreateCommand + "\n" + postStartCommand + "\n"
|
userCommand := scriptCommand + "\n" + onCreateCommand + "\n" + updateCommand + "\n" + postCreateCommand + "\n" + postStartCommand + "\n"
|
||||||
assetFS := templates.AssetFS()
|
assetFS := templates.AssetFS()
|
||||||
Content_tmpl, err := assetFS.ReadFile("repo/devcontainer/devcontainer_tmpl.sh")
|
Content_tmpl, err := assetFS.ReadFile("repo/devcontainer/devcontainer_tmpl.sh")
|
||||||
@@ -1026,7 +989,6 @@ func AddPublicKeyToAllRunningDevContainer(ctx context.Context, userId int64, pub
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(devcontainerList) > 0 {
|
if len(devcontainerList) > 0 {
|
||||||
// 将公钥写入这些打开的容器中
|
// 将公钥写入这些打开的容器中
|
||||||
for _, repoDevContainer := range devcontainerList {
|
for _, repoDevContainer := range devcontainerList {
|
||||||
|
|||||||
@@ -16,13 +16,10 @@ import (
|
|||||||
"code.gitea.io/gitea/models/repo"
|
"code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/user"
|
"code.gitea.io/gitea/models/user"
|
||||||
docker_module "code.gitea.io/gitea/modules/docker"
|
docker_module "code.gitea.io/gitea/modules/docker"
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
gitea_context "code.gitea.io/gitea/services/context"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
@@ -132,7 +129,6 @@ func CreateDevContainerByDockerCommand(ctx context.Context, newDevcontainer *dev
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageName = configurationModel.Image
|
var imageName = configurationModel.Image
|
||||||
dockerSocket, err := docker_module.GetDockerSocketPath()
|
dockerSocket, err := docker_module.GetDockerSocketPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -217,8 +213,7 @@ func CreateDevContainerByDockerCommand(ctx context.Context, newDevcontainer *dev
|
|||||||
var envFlags string = ` -e RepoLink="` + strings.TrimSuffix(cfg.Section("server").Key("ROOT_URL").Value(), `/`) + repo.Link() + `" ` +
|
var envFlags string = ` -e RepoLink="` + strings.TrimSuffix(cfg.Section("server").Key("ROOT_URL").Value(), `/`) + repo.Link() + `" ` +
|
||||||
` -e DevstarHost="` + newDevcontainer.DevcontainerHost + `"` +
|
` -e DevstarHost="` + newDevcontainer.DevcontainerHost + `"` +
|
||||||
` -e WorkSpace="` + newDevcontainer.DevcontainerWorkDir + `/` + repo.Name + `" ` +
|
` -e WorkSpace="` + newDevcontainer.DevcontainerWorkDir + `/` + repo.Name + `" ` +
|
||||||
` -e DEVCONTAINER_STATUS="start" ` +
|
` -e DEVCONTAINER_STATUS="start" `
|
||||||
` -e WEB_TERMINAL_HELLO="Successfully connected to the devcontainer" `
|
|
||||||
// 遍历 ContainerEnv 映射中的每个环境变量
|
// 遍历 ContainerEnv 映射中的每个环境变量
|
||||||
for name, value := range configurationModel.ContainerEnv {
|
for name, value := range configurationModel.ContainerEnv {
|
||||||
// 将每个环境变量转换为 "-e name=value" 格式
|
// 将每个环境变量转换为 "-e name=value" 格式
|
||||||
@@ -288,7 +283,7 @@ func CreateDevContainerByDockerCommand(ctx context.Context, newDevcontainer *dev
|
|||||||
Status: "waitting",
|
Status: "waitting",
|
||||||
UserId: newDevcontainer.UserId,
|
UserId: newDevcontainer.UserId,
|
||||||
RepoId: newDevcontainer.RepoId,
|
RepoId: newDevcontainer.RepoId,
|
||||||
Command: `docker -H ` + dockerSocket + ` exec -it --workdir ` + newDevcontainer.DevcontainerWorkDir + "/" + repo.Name + ` ` + newDevcontainer.Name + ` sh -c 'echo "$WEB_TERMINAL_HELLO";bash'` + "\n",
|
Command: `docker -H ` + dockerSocket + ` exec -it --workdir ` + newDevcontainer.DevcontainerWorkDir + "/" + repo.Name + ` ` + newDevcontainer.Name + ` sh -c "echo 'Successfully connected to the container';bash"` + "\n",
|
||||||
ListId: 4,
|
ListId: 4,
|
||||||
DevcontainerId: newDevcontainer.Id,
|
DevcontainerId: newDevcontainer.Id,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -396,16 +391,17 @@ func StopDevContainerByDocker(ctx context.Context, devContainerName string) erro
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *gitea_context.Repository, doer *user.User) error {
|
func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *repo.Repository, doer *user.User) error {
|
||||||
// 创建docker client
|
// 创建docker client
|
||||||
cli, err := docker_module.CreateDockerClient(ctx)
|
cli, err := docker_module.CreateDockerClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cli.Close()
|
defer cli.Close()
|
||||||
|
|
||||||
// update容器
|
// update容器
|
||||||
imageRef := updateInfo.RepositoryAddress + "/" + updateInfo.RepositoryUsername + "/" + updateInfo.ImageName
|
imageRef := updateInfo.RepositoryAddress + "/" + updateInfo.RepositoryUsername + "/" + updateInfo.ImageName
|
||||||
configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository)
|
configurationString, err := GetDevcontainerConfigurationString(ctx, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -415,45 +411,16 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai
|
|||||||
}
|
}
|
||||||
|
|
||||||
if updateInfo.SaveMethod == "on" {
|
if updateInfo.SaveMethod == "on" {
|
||||||
|
|
||||||
// 创建构建上下文(包含Dockerfile的tar包)
|
// 创建构建上下文(包含Dockerfile的tar包)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
tw := tar.NewWriter(&buf)
|
tw := tar.NewWriter(&buf)
|
||||||
defer tw.Close()
|
defer tw.Close()
|
||||||
// 添加Dockerfile到tar包
|
// 添加Dockerfile到tar包
|
||||||
var dockerfileContent string
|
|
||||||
dockerfile := "Dockerfile"
|
dockerfile := "Dockerfile"
|
||||||
if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" {
|
dockerfileContent, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+configurationModel.Build.Dockerfile)
|
||||||
_, err := FileExists(".devcontainer/Dockerfile", repo)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
dockerfileContent, err = GetFileContentByPath(ctx, repo.Repository, ".devcontainer/Dockerfile")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err := FileExists(".devcontainer/"+configurationModel.Build.Dockerfile, repo)
|
|
||||||
if err != nil {
|
|
||||||
if git.IsErrNotExist(err) {
|
|
||||||
_, err := FileExists(".devcontainer/Dockerfile", repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dockerfileContent, err = GetFileContentByPath(ctx, repo.Repository, ".devcontainer/Dockerfile")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
dockerfileContent, err = GetFileContentByPath(ctx, repo.Repository, ".devcontainer/"+configurationModel.Build.Dockerfile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content := []byte(dockerfileContent)
|
content := []byte(dockerfileContent)
|
||||||
header := &tar.Header{
|
header := &tar.Header{
|
||||||
Name: dockerfile,
|
Name: dockerfile,
|
||||||
@@ -501,12 +468,11 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义正则表达式来匹配 image 字段
|
// 定义正则表达式来匹配 image 字段
|
||||||
re := regexp.MustCompile(`"image"\s*:\s*"([^"]+)"`)
|
re := regexp.MustCompile(`"image"\s*:\s*"([^"]+)"`)
|
||||||
// 使用正则表达式查找并替换 image 字段的值
|
// 使用正则表达式查找并替换 image 字段的值
|
||||||
newConfiguration := re.ReplaceAllString(configurationString, `"image": "`+imageRef+`"`)
|
newConfiguration := re.ReplaceAllString(configurationString, `"image": "`+imageRef+`"`)
|
||||||
err = UpdateDevcontainerConfiguration(newConfiguration, repo.Repository, doer)
|
err = UpdateDevcontainerConfiguration(newConfiguration, repo, doer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -518,6 +484,7 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai
|
|||||||
// - bool: 镜像是否存在(true=存在,false=不存在)
|
// - bool: 镜像是否存在(true=存在,false=不存在)
|
||||||
// - error: 非空表示检查过程中发生错误
|
// - error: 非空表示检查过程中发生错误
|
||||||
func ImageExists(ctx context.Context, imageName string) (bool, error) {
|
func ImageExists(ctx context.Context, imageName string) (bool, error) {
|
||||||
|
|
||||||
// 创建 Docker 客户端
|
// 创建 Docker 客户端
|
||||||
cli, err := docker_module.CreateDockerClient(ctx)
|
cli, err := docker_module.CreateDockerClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -552,6 +519,7 @@ func CheckDirExistsFromDocker(ctx context.Context, containerName, dirPath string
|
|||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建 exec 实例
|
// 创建 exec 实例
|
||||||
execResp, err := cli.ContainerExecCreate(context.Background(), containerID, execConfig)
|
execResp, err := cli.ContainerExecCreate(context.Background(), containerID, execConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -574,7 +542,6 @@ func CheckDirExistsFromDocker(ctx context.Context, containerName, dirPath string
|
|||||||
exitCode = resp.ExitCode
|
exitCode = resp.ExitCode
|
||||||
return exitCode == 0, nil // 退出码为 0 表示目录存在
|
return exitCode == 0, nil // 退出码为 0 表示目录存在
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckFileExistsFromDocker(ctx context.Context, containerName, filePath string) (bool, error) {
|
func CheckFileExistsFromDocker(ctx context.Context, containerName, filePath string) (bool, error) {
|
||||||
// 上下文
|
// 上下文
|
||||||
// 创建 Docker 客户端
|
// 创建 Docker 客户端
|
||||||
@@ -631,7 +598,7 @@ func RegistWebTerminal(ctx context.Context) error {
|
|||||||
// 拉取镜像
|
// 拉取镜像
|
||||||
err = docker_module.PullImage(ctx, cli, dockerHost, setting.DevContainerConfig.Web_Terminal_Image)
|
err = docker_module.PullImage(ctx, cli, dockerHost, setting.DevContainerConfig.Web_Terminal_Image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Errorf("拉取web_terminal镜像失败:%v", err)
|
return fmt.Errorf("拉取web_terminal镜像失败:%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp := time.Now().Format("20060102150405")
|
timestamp := time.Now().Format("20060102150405")
|
||||||
@@ -665,36 +632,3 @@ func RegistWebTerminal(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerExists 检查容器是否存在,返回存在状态和容器ID(如果存在)
|
|
||||||
func ContainerExists(ctx context.Context, containerName string) (bool, string, error) {
|
|
||||||
cli, err := docker_module.CreateDockerClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
// 设置过滤器,根据容器名称过滤
|
|
||||||
filter := filters.NewArgs()
|
|
||||||
filter.Add("name", containerName)
|
|
||||||
|
|
||||||
// 获取容器列表,使用过滤器
|
|
||||||
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{
|
|
||||||
All: true, // 包括所有容器(运行的和停止的)
|
|
||||||
Filters: filter,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 遍历容器,检查名称是否完全匹配
|
|
||||||
for _, container := range containers {
|
|
||||||
for _, name := range container.Names {
|
|
||||||
// 容器名称在Docker API中是以斜杠开头的,例如 "/my-container"
|
|
||||||
// 所以我们需要检查去掉斜杠后的名称是否匹配
|
|
||||||
if strings.TrimPrefix(name, "/") == containerName {
|
|
||||||
return true, container.ID, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, "", nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ type InstallForm struct {
|
|||||||
RequireSignInView bool
|
RequireSignInView bool
|
||||||
DefaultKeepEmailPrivate bool
|
DefaultKeepEmailPrivate bool
|
||||||
DefaultAllowCreateOrganization bool
|
DefaultAllowCreateOrganization bool
|
||||||
DefaultAllowCreateDevcontainer bool
|
|
||||||
DefaultEnableTimetracking bool
|
DefaultEnableTimetracking bool
|
||||||
EnableUpdateChecker bool
|
EnableUpdateChecker bool
|
||||||
NoReplyAddress string
|
NoReplyAddress string
|
||||||
|
|||||||
@@ -153,8 +153,6 @@
|
|||||||
<dd>{{svg (Iif .Service.DefaultKeepEmailPrivate "octicon-check" "octicon-x")}}</dd>
|
<dd>{{svg (Iif .Service.DefaultKeepEmailPrivate "octicon-check" "octicon-x")}}</dd>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.default_allow_create_organization"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.default_allow_create_organization"}}</dt>
|
||||||
<dd>{{svg (Iif .Service.DefaultAllowCreateOrganization "octicon-check" "octicon-x")}}</dd>
|
<dd>{{svg (Iif .Service.DefaultAllowCreateOrganization "octicon-check" "octicon-x")}}</dd>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.default_allow_create_devcontainer"}}</dt>
|
|
||||||
<dd>{{svg (Iif .Service.DefaultAllowCreateDevcontainer "octicon-check" "octicon-x")}}</dd>
|
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.enable_timetracking"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.enable_timetracking"}}</dt>
|
||||||
<dd>{{svg (Iif .Service.EnableTimetracking "octicon-check" "octicon-x")}}</dd>
|
<dd>{{svg (Iif .Service.EnableTimetracking "octicon-check" "octicon-x")}}</dd>
|
||||||
{{if .Service.EnableTimetracking}}
|
{{if .Service.EnableTimetracking}}
|
||||||
|
|||||||
@@ -304,12 +304,6 @@
|
|||||||
<input name="default_allow_create_organization" type="checkbox" {{if .default_allow_create_organization}}checked{{end}}>
|
<input name="default_allow_create_organization" type="checkbox" {{if .default_allow_create_organization}}checked{{end}}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline field">
|
|
||||||
<div class="ui checkbox">
|
|
||||||
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_allow_create_devcontainer_popup"}}">{{ctx.Locale.Tr "install.default_allow_create_devcontainer"}}</label>
|
|
||||||
<input name="default_allow_create_devcontainer" type="checkbox" {{if .default_allow_create_devcontainer}}checked{{end}}>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_enable_timetracking_popup"}}">{{ctx.Locale.Tr "install.default_enable_timetracking"}}</label>
|
<label data-tooltip-content="{{ctx.Locale.Tr "install.default_enable_timetracking_popup"}}">{{ctx.Locale.Tr "install.default_enable_timetracking"}}</label>
|
||||||
|
|||||||
@@ -14,10 +14,6 @@
|
|||||||
"echo \"postCreateCommand\"",
|
"echo \"postCreateCommand\"",
|
||||||
"echo \"OK\""
|
"echo \"OK\""
|
||||||
],
|
],
|
||||||
"postAttachCommand": [
|
|
||||||
"echo \"postAttachCommand\"",
|
|
||||||
"echo \"OK\""
|
|
||||||
],
|
|
||||||
"runArgs": [
|
"runArgs": [
|
||||||
"-p 8888"
|
"-p 8888"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
{{else}}
|
{{else}}
|
||||||
|
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
|
||||||
<form class="ui edit form">
|
<form class="ui edit form">
|
||||||
<div class="repo-editor-header">
|
<div class="repo-editor-header">
|
||||||
<div class="ui breadcrumb field">
|
<div class="ui breadcrumb field">
|
||||||
@@ -37,9 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
{{if and .ValidateDevContainerConfiguration .HasDevContainer}}
|
|
||||||
<iframe id="webTerminalContainer" src="{{.WebSSHUrl}}" width="100%" style="height: 100vh; display: none;" frameborder="0">您的浏览器不支持iframe</iframe>
|
<iframe id="webTerminalContainer" src="{{.WebSSHUrl}}" width="100%" style="height: 100vh; display: none;" frameborder="0">您的浏览器不支持iframe</iframe>
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
@@ -50,7 +47,7 @@
|
|||||||
<strong>{{ctx.Locale.Tr "repo.dev_container_control"}}</strong>
|
<strong>{{ctx.Locale.Tr "repo.dev_container_control"}}</strong>
|
||||||
<div class="ui relaxed list">
|
<div class="ui relaxed list">
|
||||||
|
|
||||||
{{if and .ValidateDevContainerConfiguration .HasDevContainer}}
|
{{if .HasDevContainer}}
|
||||||
<div style=" display: none;" id="deleteContainer" class="item"><a class="delete-button flex-text-inline" data-modal="#delete-repo-devcontainer-of-user-modal" href="#" data-url="{{.Repository.Link}}/devcontainer/delete">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.dev_container_control.delete"}}</a></div>
|
<div style=" display: none;" id="deleteContainer" class="item"><a class="delete-button flex-text-inline" data-modal="#delete-repo-devcontainer-of-user-modal" href="#" data-url="{{.Repository.Link}}/devcontainer/delete">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.dev_container_control.delete"}}</a></div>
|
||||||
{{if .isAdmin}}
|
{{if .isAdmin}}
|
||||||
<div style=" display: none;" id="updateContainer" class="item"><a class="delete-button flex-text-inline" style="color:black; " data-modal-id="updatemodal" href="#">{{svg "octicon-database"}}{{ctx.Locale.Tr "repo.dev_container_control.update"}}</a></div>
|
<div style=" display: none;" id="updateContainer" class="item"><a class="delete-button flex-text-inline" style="color:black; " data-modal-id="updatemodal" href="#">{{svg "octicon-database"}}{{ctx.Locale.Tr "repo.dev_container_control.update"}}</a></div>
|
||||||
@@ -69,7 +66,7 @@
|
|||||||
<div style=" display: none;" id="createContainer" class="item">
|
<div style=" display: none;" id="createContainer" class="item">
|
||||||
<div>
|
<div>
|
||||||
<form method="get" action="{{.Repository.Link}}/devcontainer/create" class="ui edit form">
|
<form method="get" action="{{.Repository.Link}}/devcontainer/create" class="ui edit form">
|
||||||
<button class="flex-text-inline" type="submit">{{svg "octicon-terminal" 14 "tw-mr-2"}} {{ctx.Locale.Tr "repo.dev_container_control.create"}}</button>
|
<button class="flex-text-inline" type="submit">{{svg "octicon-terminal" 14 "tw-mr-2"}} Create Dev Container</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,16 +84,6 @@
|
|||||||
<!-- 结束Dev Container 正文内容 -->
|
<!-- 结束Dev Container 正文内容 -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 自定义警告框 -->
|
|
||||||
<div id="customAlert" class="custom-alert">
|
|
||||||
<div class="alert-content">
|
|
||||||
<div class="alert-header">
|
|
||||||
<strong>提示信息</strong>
|
|
||||||
<button class="alert-close" onclick="closeCustomAlert()">×</button>
|
|
||||||
</div>
|
|
||||||
<div id="alertText" class="alert-body"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 确认删除 Dev Container 模态对话框 -->
|
<!-- 确认删除 Dev Container 模态对话框 -->
|
||||||
<div class="ui g-modal-confirm delete modal" id="delete-repo-devcontainer-of-user-modal">
|
<div class="ui g-modal-confirm delete modal" id="delete-repo-devcontainer-of-user-modal">
|
||||||
@@ -109,14 +96,24 @@
|
|||||||
</div>
|
</div>
|
||||||
{{template "base/modal_actions_confirm" .}}
|
{{template "base/modal_actions_confirm" .}}
|
||||||
</div>
|
</div>
|
||||||
<!-- 保存 Dev Container 模态对话框 -->
|
<!-- 确认 Dev Container 模态对话框 -->
|
||||||
<div class="ui g-modal-confirm delete modal" style="width: 35%" id="updatemodal">
|
<div class="ui g-modal-confirm delete modal" style="width: 35%" id="updatemodal">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
{{ctx.Locale.Tr "repo.dev_container_control.update"}}
|
{{ctx.Locale.Tr "repo.dev_container_control.update"}}
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<form class="ui form tw-max-w-2xl tw-m-auto" id="updateForm" onsubmit="submitForm(event)">
|
<form class="ui form tw-max-w-2xl tw-m-auto" id="updateForm" onsubmit="submitForm(event)">
|
||||||
|
<div class="inline field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
{{if not .HasDevContainerDockerfile}}
|
||||||
|
<input type="checkbox" id="SaveMethod" name="SaveMethod" disabled>
|
||||||
|
{{else}}
|
||||||
|
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on">
|
||||||
|
{{end}}
|
||||||
|
<label for="SaveMethod">Build From Dockerfile</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="required field ">
|
<div class="required field ">
|
||||||
<label for="RepositoryAddress">Registry:</label>
|
<label for="RepositoryAddress">Registry:</label>
|
||||||
<input style="border: 1px solid black;" type="text" id="RepositoryAddress" name="RepositoryAddress" value="{{.RepositoryAddress}}">
|
<input style="border: 1px solid black;" type="text" id="RepositoryAddress" name="RepositoryAddress" value="{{.RepositoryAddress}}">
|
||||||
@@ -127,38 +124,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="required field ">
|
<div class="required field ">
|
||||||
<label for="RepositoryPassword">Registry Password:</label>
|
<label for="RepositoryPassword">Registry Password:</label>
|
||||||
<div style="position: relative; display: inline-block; width: 100%;">
|
<input style="border: 1px solid black;" type="text" id="RepositoryPassword" name="RepositoryPassword" required>
|
||||||
<input style="border: 1px solid black; width: 100%; padding-right: 80px;"
|
|
||||||
type="password"
|
|
||||||
id="RepositoryPassword"
|
|
||||||
name="RepositoryPassword"
|
|
||||||
required
|
|
||||||
autocomplete="current-password">
|
|
||||||
<button type="button"
|
|
||||||
style="position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
|
|
||||||
background: none; border: none; cursor: pointer; color: #666;
|
|
||||||
font-size: 12px; padding: 5px 8px;"
|
|
||||||
onclick="togglePasswordVisibility('RepositoryPassword', this)">
|
|
||||||
显示密码
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="required field ">
|
<div class="required field ">
|
||||||
<label for="ImageName">Image(name:tag):</label>
|
<label for="ImageName">Image(name:tag):</label>
|
||||||
<input style="border: 1px solid black;" type="text" id="ImageName" name="ImageName" value="{{.ImageName}}">
|
<input style="border: 1px solid black;" type="text" id="ImageName" name="ImageName" value="{{.ImageName}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="inline field">
|
|
||||||
<div class="ui checkbox">
|
|
||||||
{{if not .HasDevContainerDockerfile}}
|
|
||||||
<input type="checkbox" id="SaveMethod" name="SaveMethod" disabled>
|
|
||||||
<label for="SaveMethod">There is no Dockerfile</label>
|
|
||||||
{{else}}
|
|
||||||
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on">
|
|
||||||
<label for="SaveMethod">Build From Dockerfile: {{.DockerfilePath}}</label>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="ui primary button" type="submit" id="updateSubmitButton" >Submit</button>
|
<button class="ui primary button" type="submit" id="updateSubmitButton" >Submit</button>
|
||||||
<button class="ui cancel button" id="updateCloseButton">Close</button>
|
<button class="ui cancel button" id="updateCloseButton">Close</button>
|
||||||
@@ -171,21 +143,6 @@
|
|||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('updateSubmitButton').addEventListener('click', function() {
|
|
||||||
const form = document.getElementById('updateForm');
|
|
||||||
const formData = new FormData(form);
|
|
||||||
var RepositoryAddress = formData.get('RepositoryAddress');
|
|
||||||
var RepositoryUsername = formData.get('RepositoryUsername');
|
|
||||||
var RepositoryPassword = formData.get('RepositoryPassword');
|
|
||||||
var SaveMethod = formData.get('SaveMethod');
|
|
||||||
var ImageName = formData.get('ImageName');
|
|
||||||
if(ImageName != "" && SaveMethod != "" && RepositoryPassword != "" && RepositoryUsername != "" && RepositoryAddress != ""){
|
|
||||||
document.getElementById('updatemodal').classList.add('is-loading')
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var status = '-1'
|
var status = '-1'
|
||||||
var intervalID
|
var intervalID
|
||||||
const createContainer = document.getElementById('createContainer');
|
const createContainer = document.getElementById('createContainer');
|
||||||
@@ -276,13 +233,13 @@ function getStatus() {
|
|||||||
if(status !== '9' && status !== '-1' && data.status == '9'){
|
if(status !== '9' && status !== '-1' && data.status == '9'){
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
else if(status !== '-1' && data.status == '-1'){
|
if(status !== '-1' && data.status == '-1'){
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
else if(status !== '4' && status !== '-1' && data.status == '4'){
|
if(status !== '4' && status !== '-1' && data.status == '4'){
|
||||||
//window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
else if (data.status == '-1' || data.status == '') {
|
if (data.status == '-1' || data.status == '') {
|
||||||
if (loadingElement) {
|
if (loadingElement) {
|
||||||
loadingElement.style.display = 'none';
|
loadingElement.style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -376,7 +333,7 @@ function getStatus() {
|
|||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
intervalID = setInterval(getStatus, 5000);
|
intervalID = setInterval(getStatus, 3000);
|
||||||
if (restartContainer) {
|
if (restartContainer) {
|
||||||
restartContainer.addEventListener('click', function(event) {
|
restartContainer.addEventListener('click', function(event) {
|
||||||
// 处理点击逻辑
|
// 处理点击逻辑
|
||||||
@@ -385,7 +342,7 @@ if (restartContainer) {
|
|||||||
loadingElement.style.display = 'block';
|
loadingElement.style.display = 'block';
|
||||||
}
|
}
|
||||||
fetch('{{.Repository.Link}}' + '/devcontainer/restart')
|
fetch('{{.Repository.Link}}' + '/devcontainer/restart')
|
||||||
.then(response => {intervalID = setInterval(getStatus, 5000);})
|
.then(response => {intervalID = setInterval(getStatus, 3000);})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (stopContainer) {
|
if (stopContainer) {
|
||||||
@@ -396,7 +353,7 @@ if (stopContainer) {
|
|||||||
}
|
}
|
||||||
// 处理点击逻辑
|
// 处理点击逻辑
|
||||||
fetch('{{.Repository.Link}}' + '/devcontainer/stop')
|
fetch('{{.Repository.Link}}' + '/devcontainer/stop')
|
||||||
.then(response => {intervalID = setInterval(getStatus, 5000);})
|
.then(response => {intervalID = setInterval(getStatus, 3000);})
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -406,46 +363,10 @@ if (deleteContainer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function togglePasswordVisibility(passwordFieldId, button) {
|
|
||||||
const passwordInput = document.getElementById(passwordFieldId);
|
|
||||||
|
|
||||||
if (passwordInput.type === 'password') {
|
|
||||||
passwordInput.type = 'text';
|
|
||||||
button.textContent = '隐藏密码';
|
|
||||||
button.style.color = '#2185d0'; // 主色调,表示激活状态
|
|
||||||
} else {
|
|
||||||
passwordInput.type = 'password';
|
|
||||||
button.textContent = '显示密码';
|
|
||||||
button.style.color = '#666'; // 恢复默认颜色
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function showCustomAlert(message, title = "提示信息") {
|
|
||||||
const alertBox = document.getElementById('customAlert');
|
|
||||||
const alertText = document.getElementById('alertText');
|
|
||||||
const alertHeader = alertBox.querySelector('.alert-header strong');
|
|
||||||
|
|
||||||
alertHeader.textContent = title;
|
|
||||||
alertText.textContent = message;
|
|
||||||
alertBox.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeCustomAlert() {
|
|
||||||
document.getElementById('customAlert').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点击背景关闭
|
|
||||||
document.getElementById('customAlert').addEventListener('click', function(e) {
|
|
||||||
if (e.target === this) {
|
|
||||||
closeCustomAlert();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function submitForm(event) {
|
function submitForm(event) {
|
||||||
event.preventDefault(); // 阻止默认的表单提交行为
|
event.preventDefault(); // 阻止默认的表单提交行为
|
||||||
const {csrfToken} = window.config;
|
const {csrfToken} = window.config;
|
||||||
const {appSubUrl} = window.config;
|
const {appSubUrl} = window.config;
|
||||||
const formModal = document.getElementById('updatemodal');
|
|
||||||
const form = document.getElementById('updateForm');
|
const form = document.getElementById('updateForm');
|
||||||
const submitButton = document.getElementById('updateSubmitButton');
|
const submitButton = document.getElementById('updateSubmitButton');
|
||||||
const closeButton = document.getElementById('updateCloseButton');
|
const closeButton = document.getElementById('updateCloseButton');
|
||||||
@@ -469,10 +390,9 @@ function submitForm(event) {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
submitButton.disabled = false;
|
submitButton.disabled = false;
|
||||||
formModal.classList.remove('is-loading')
|
alert(data.message);
|
||||||
showCustomAlert(data.message);
|
|
||||||
if(data.redirect){
|
if(data.redirect){
|
||||||
closeCustomAlert()
|
closeButton.click()
|
||||||
}
|
}
|
||||||
intervalID = setInterval(getStatus, 3000);
|
intervalID = setInterval(getStatus, 3000);
|
||||||
})
|
})
|
||||||
@@ -502,69 +422,6 @@ function submitForm(event) {
|
|||||||
0%{-webkit-transform:rotate(0deg)}
|
0%{-webkit-transform:rotate(0deg)}
|
||||||
100%{-webkit-transform:rotate(360deg)}
|
100%{-webkit-transform:rotate(360deg)}
|
||||||
}
|
}
|
||||||
.custom-alert {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(0,0,0,0.5);
|
|
||||||
z-index: 10000;
|
|
||||||
}
|
|
||||||
.alert-content {
|
|
||||||
color: black;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
background: white;
|
|
||||||
padding: 0; /* 移除内边距,在内部元素中设置 */
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 80%;
|
|
||||||
max-width: 600px;
|
|
||||||
max-height: 80%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
.alert-header {
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 10;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.alert-close {
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #666;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.alert-close:hover {
|
|
||||||
background: #e9ecef;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.alert-body {
|
|
||||||
padding: 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: calc(80vh - 100px); /* 减去头部高度 */
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
|||||||
Reference in New Issue
Block a user