通过 SSH 隧道实现访问内网机器

适用场景:本地无法直接 ssh 到内网机器,如果内网机器可以访问公网,就可以通过公网的机器打通 ssh 隧道进行访问

假设 IP 信息如下:

  1. 内网机器:172.16.0.1
  2. 公网机器:1.2.3.4

首先需要确认公网机器的 ssh 配置允许反向隧道

1
2
cat /etc/ssh/sshd_config | grep GatewayPorts
GatewayPorts yes

在内网机器上,与公网机器进行隧道打通,这里的 ssh 认证使用公网机器的用户名密码

1
2
3
# -N 表示不执行远程命令,仅用于转发端口
# -R 用于设置反向隧道,本示例中会将公网机器的 2222 端口转发到内网机器的 22 端口
ssh -N -R 2222:0.0.0.0:22 root@1.2.3.4

然后在本地,ssh 到公网机器的 2222 端口即可,这里的 ssh 认证使用内网机器的用户名密码

1
ssh root@1.2.3.4 -p 2222

ETCD 出现高碎片率事件解析

集群频繁触发 etcdDatabaseHighFragmentationRatio 告警, PrometheusRule 内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- alert: etcdDatabaseHighFragmentationRatio
annotations:
description: 'etcd cluster "{{ $labels.job }}": database size in use on instance
{{ $labels.instance }} is {{ $value | humanizePercentage }} of the actual
allocated disk space, please run defragmentation (e.g. etcdctl defrag) to
retrieve the unused fragmented disk space.'
runbook_url: https://etcd.io/docs/v3.5/op-guide/maintenance/#defragmentation
summary: etcd database size in use is less than 50% of the actual allocated
storage.
expr: (last_over_time(etcd_mvcc_db_total_size_in_use_in_bytes{job=~".*etcd.*"}[5m])
/ last_over_time(etcd_mvcc_db_total_size_in_bytes{job=~".*etcd.*"}[5m])) <
0.5 and etcd_mvcc_db_total_size_in_use_in_bytes{job=~".*etcd.*"} > 104857600
for: 10m
labels:
severity: warning

相关指标:

  1. etcd_server_quota_backend_bytes:当前后端存储配额大小(字节),默认为 2GB
  2. etcd_mvcc_db_total_size_in_bytes:物理分配的底层数据库总大小(字节),包含了数据(如 keyspace)和碎片,即 DB SIZE
  3. etcd_mvcc_db_total_size_in_use_in_bytes:逻辑上正在使用的底层数据库的总大小(以字节为单位),不包含碎片

也就是说 quota-backend-bytes 配置后,etcd_mvcc_db_total_size_in_bytes 的大小并不会根据这个值而变化,会变的是 etcd_server_quota_backend_bytes,etcd_mvcc_db_total_size_in_bytes 的值指的是 DB SIZE,可以通过以下方式获取 DB SIZE

1
2
ls -lrth ${etcd-data-dir}/member/snap
etcdctl endpoint status -w table

为什么会产生碎片?

  1. ETCD 支持多版本并发控制(MVCC),同时会精确记录其 keyspace 的历史
  2. 压缩操作是清除历史记录的唯一方法,通常用 –auto-compaction-mode 和 –auto-compaction-retention 来实现自动压缩
  3. 但压缩操作后的空闲空间并不会真正在文件系统中释放,而是会被 ETCD 标记为可使用的空闲空间,也就是说压缩操作后仍然会占用磁盘空间
  4. 要真正释放,就需要进行碎片整理,即 etcdctl defrag
1
etcdctl defrag

那么针对 etcdDatabaseHighFragmentationRatio 告警的触发,要怎么判断需不需要进行碎片清理?

  1. 通过 etcd_server_quota_backend_bytes 指标查看实际配额
  2. 通过指标 etcd_mvcc_db_total_size_in_bytes 或者命令检查 DB SIZE,看是否真的很大且接近于 quota-backend-bytes,如果不是则无需担心
  3. 如果是,获取每种资源的数量,查看是什么资源导致 DB Size 这么大,然后通过碎片清理尝试释放空间,如果释放后仍接近于 quota-backend-bytes,那么需要考虑增加配额
    1
    2
    etcdctl get /registry --prefix --keys-only | grep -v ^$ | awk -F '/'  '{ h[$3]++ } END {for (k in h) print h[k], k}' | sort -nr
    etcdctl defrag

Cert Manager 使用随记

在 Kubernetes 中常用 Cert Manager 生成并管理自签名证书,常见的 CR 有👇

  1. Issuer: 用于定义如何生成证书
  2. ClusterIssuer: 用于定义如何生成集群级别的证书
  3. Certificate: 用来请求和管理证书的主要资源
  4. CertificateRequest: 是用于手动请求证书的资源
  5. Order: 当使用 ACME 协议时会生成,
  6. Challenge: 是 ACME 协议中的一部分,用于表示 ACME 服务对域名所有权的验证

一个自签的简单实用例子

自签名生成 CA 证书和私钥

1
2
3
4
5
6
7
8
9
# 生成 CA 的私钥
openssl genrsa -out ca.key 2048

# 生成自签名的 CA 证书
openssl req -x509 -new -nodes -key ca.key -subj "/CN=nginx-ca" -days 365 -out ca.crt

kubectl create secret tls tls-nginx \
--cert=ca.crt \
--key=ca.key

通过 Issue 创建自签名

1
2
3
4
5
6
7
8
9
10
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: nginx-issuer
namespace: default
spec:
ca:
secretName: tls-nginx
EOF

通过 Certificate 创建证书请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: nginx-certificate
namespace: default
spec:
secretName: tls-nginx
duration: 24h
renewBefore: 12h
commonName: nginx.warnerchen.io
dnsNames:
- nginx.warnerchen.io
issuerRef:
name: nginx-issuer
kind: Issuer
EOF

给 Ingress 挂载后,尝试请求

请求Ingress

RKE1 部署随记

部署 RKE1

前期准备

1
2
3
4
5
6
7
# RKE1 二进制
curl -LO "https://github.com/rancher/rke/releases/download/v1.5.12/rke_linux-amd64"

mv rke_linux-amd64 /usr/local/bin/rke && chmod +x /usr/local/bin/rke

# 各节点安装 Docker
curl https://releases.rancher.com/install-docker/20.10.sh | sh

生成配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
cat <<EOF > cluster.yml
# 旧版本 rke1 私钥类型不支持 rsa,需要选择 ed25519
ssh_key_path: /root/.ssh/id_ed25519
nodes:
- address: 172.16.0.106
hostname_override: rke1-server-0
internal_address: 172.16.0.106
user: root
role:
- controlplane
- etcd
- worker
- address: 172.16.0.105
hostname_override: rke1-server-1
internal_address: 172.16.0.105
user: root
role:
- controlplane
- etcd
- worker
- address: 172.16.0.104
hostname_override: rke1-server-2
internal_address: 172.16.0.104
user: root
role:
- controlplane
- etcd
- worker
private_registries:
- url: registry.cn-hangzhou.aliyuncs.com
is_default: true
kubernetes_version: "v1.20.15-rancher2-2"
network:
plugin: calico
EOF

安装 RKE1

1
rke up --config cluster.yml

方便后续运维配置

1
2
3
4
5
6
7
8
9
10
11
mkdir ~/.kube

mv kube_config_cluster.yml ~/.kube/config

find / -name kubectl | grep "/usr/local" | head -1 | awk '{ print "cp "$1" /usr/local/bin" }' | sh

echo "source <(kubectl completion bash)" >> ~/.bashrc

curl https://rancher-mirror.rancher.cn/helm/get-helm-3.sh | INSTALL_HELM_MIRROR=cn bash -s -- --version v3.10.3

echo "source <(helm completion bash)" >> ~/.bashrc

常见问题

如果是 CentOS 和 RHEL 系统,默认不允许使用 root 用户进行安装,报错信息如下:

1
WARN[0000] Failed to set up SSH tunneling for host [x.x.x.x]: Can’t retrieve Docker Info ,Failed to dial to /var/run/docker.sock: ssh: rejected: administratively prohibited (open failed)

需要准备其他用户:

1
groupadd rancher && useradd rancher -g rancher && usermod -aG docker rancher

如果出现下面错误,是由于指定的 ssh_key_path 文件对应的主机不正确或对应的用户名不正确,可以检查下节点对应用户的 ~/.ssh/authorized_keys 文件是否正确:

1
WARN[0000] Failed to set up SSH tunneling for host [x.x.x.x]: Can't retrieve Docker Info: error during connect: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/info": Unable to access node with address [x.x.x.x:22] using SSH. Please check if you are able to SSH to the node using the specified SSH Private Key and if you have configured the correct SSH username. Error: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain

如果出现下面错误:

1
WARN[0000] Failed to set up SSH tunneling for host [x.x.x.x]: Can't retrieve Docker Info: error during connect: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.24/info: Unable to access the service on /var/run/docker.sock. The service might be still starting up. Error: ssh: rejected: connect failed (open failed) 

需要在 /etc/ssh/sshd_config 文件中添加以下内容:

1
AllowTcpForwarding yes

清理 iptables 规则

1
2
3
4
5
6
7
iptables -F \
&& iptables -X \
&& iptables -Z \
&& iptables -F -t nat \
&& iptables -X -t nat \
&& iptables -Z -t nat \
&& docker restart kube-proxy

清理节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
sudo docker rm -f $(sudo docker ps -qa)
sudo docker rmi -f $(sudo docker images -q)
sudo docker volume rm $(sudo docker volume ls -q)

for mount in $(sudo mount | grep tmpfs | grep '/var/lib/kubelet' | awk '{ print $3 }') /var/lib/kubelet /var/lib/rancher; do sudo umount $mount; done

sudo rm -rf /etc/ceph \
/etc/cni \
/etc/kubernetes \
/etc/rancher \
/opt/cni \
/opt/rke \
/run/secrets/kubernetes.io \
/run/calico \
/run/flannel \
/var/lib/calico \
/var/lib/etcd \
/var/lib/cni \
/var/lib/kubelet \
/var/lib/rancher\
/var/log/containers \
/var/log/kube-audit \
/var/log/pods \
/var/run/calico

sudo reboot

调用 NeuVector API 进行镜像扫描

开启 REST API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: neuvector-service-controller
namespace: cattle-neuvector-system
spec:
ports:
- port: 10443
name: controller
protocol: TCP
type: NodePort
selector:
app: neuvector-controller-pod
EOF

准备一些调用接口所需的环境变量

1
2
3
4
5
6
7
8
9
nv_service_ip="neuvector-service-controller"
nv_service_port="10443"
nv_service_login_user="admin"
nv_service_login_password="admin"
image_registry_url="https://xxx"
image_registry_user="xxx"
image_registry_password="xxx"
image_repo="library/nginx"
image_tag="mainline"

调用接口进行镜像扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# NV 认证 API
api_login_url="https://$nv_service_ip:$nv_service_port/v1/auth"
echo $api_login_url

# 定义 NV 认证参数
login_json="{\"password\":{\"username\":\"$nv_service_login_user\",\"password\":\"$nv_service_login_password\"}}"
echo $login_json

# 获取 NV 认证 token
nv_token=`(curl -s -f $api_login_url -k -H "Content-Type:application/json" -d $login_json || echo null) | jq -r '.token.token'`
echo $nv_token

# 镜像扫描 API
api_scan_repo_url="https://$nv_service_ip:$nv_service_port/v1/scan/repository"
echo $api_scan_repo_url

# 定义镜像扫描参数
nv_scanned_json="{\"request\": {\"registry\": \"$image_registry_url\", \"username\": \"$image_registry_user\", \"password\": \"$image_registry_password\", \"repository\": \"$image_repo\", \"tag\": \"$image_tag\"}}"
echo $nv_scanned_json

# 调用镜像扫描 API
curl -k "$api_scan_repo_url" -H "Content-Type: application/json" -H "X-Auth-Token: $nv_token" -d "$nv_scanned_json"

registry 为空的时候,NeuVector 会对本地镜像进行扫描,但只支持在 allinone 下使用,如果是在 K8s 部署的 NV 中调用接口进行本地扫描,会出现报错:

1
2
2024-11-06T09:14:15.179|INFO|CTL|rest.(*repoScanTask).Run: Scan repository start - image=library/nginx:mainline registry=
2024-11-06T09:14:15.24 |ERRO|CTL|rest.(*repoScanTask).Run: Failed to scan repository - error=container API call error image=library/nginx:mainline registry=

NeuVector 除了调用 API 接口进行镜像扫描外,还可以使用 Assets -> Registries 对接镜像仓库进行扫描,如果存在 Image scanned = false 的 Admission Control,只要完成两种扫描方式的其中一种,就可以顺利完成部署而不被规则所拦截。