分类目录归档:技术

自建 gitlab 徽标问题导致项目 500 问题解决

问题

最近内部 gitlab 某些项目打开就 500 了, 看 gitlab 报错日志如下:

gitlab  | {"method":"GET","path":"/xxx/xxx","format":"html","controller":"ProjectsController","action":"show","status":500,"time":"2025-08-28T00:51:41.511Z","params":[{"key":"namespace_id","value":"xxx"},{"key":"id","value":"xxx"}],"remote_ip":"10.17.7.63","user_id":74,"username":"xxxgitlab | {"method":"GET","path":"/xxx/xxx","format":"html","controller":"ProjectsController","action":"show","status":500,"time":"2025-08-28T00:51:41.511Z","params":[{"key":"namespace_id","value":"xxx"},{"key":"id","value":"xxx"}],"remote_ip":"10.17.7.63","user_id":74,"username":"xxx","ua":"Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0","correlation_id":"01K3Q2F5DXP8Y33B25TW9SSMYM","meta.user":"xxx","meta.project":"xxx/xxx","meta.root_namespace":"storage","meta.caller_id":"ProjectsController#show","meta.remote_ip":"10.17.7.63","meta.feature_category":"projects","meta.client_id":"user/74","redis_calls":21,"redis_duration_s":0.007123,"redis_read_bytes":2997,"redis_write_bytes":2370,"redis_cache_calls":20,"redis_cache_duration_s":0.006591,"redis_cache_read_bytes":2816,"redis_cache_write_bytes":1035,"redis_shared_state_calls":1,"redis_shared_state_duration_s":0.000532,"redis_shared_state_read_bytes":181,"redis_shared_state_write_bytes":1335,"db_count":41,"db_write_count":0,"db_cached_count":10,"cpu_s":2.291446,"mem_objects":394125,"mem_bytes":52397272,"mem_mallocs":198648,"mem_total_bytes":68162272,"queue_duration_s":0.009214,"exception.class":"Rack::Timeout::RequestTimeoutException","exception.message":"Request ran for longer than 60000ms","exception.backtrace":["lib/gitlab/url_blocker.rb:113:in `getaddrinfo'","lib/gitlab/url_blocker.rb:113:in `get_address_info'","lib/gitlab/url_blocker.rb:48:in `validate!'","app/validators/addressable_url_validator.rb:83:in `validate_each'","app/models/badge.rb:43:in `build_rendered_url'","app/models/badge.rb:36:in `rendered_image_url'","app/models/badges/project_badge.rb:15:in `rendered_image_url'","app/views/projects/_home_panel.html.haml:93","app/views/projects/_home_panel.html.haml:89","app/views/projects/_home_panel.html.haml:87","app/views/projects/show.html.haml:14","app/controllers/application_controller.rb:128:in `render'","app/controllers/application_controller.rb:538:in `block in allow_gitaly_ref_name_caching'","lib/gitlab/gitaly_client.rb:341:in `allow_ref_name_caching'","app/controllers/application_controller.rb:537:in `allow_gitaly_ref_name_caching'","app/controllers/application_controller.rb:487:in `set_current_admin'","lib/gitlab/session.rb:11:in `with_session'","app/controllers/application_controller.rb:478:in `set_session_storage'","lib/gitlab/i18n.rb:99:in `with_locale'","lib/gitlab/i18n.rb:105:in `with_user_locale'","app/controllers/application_controller.rb:472:in `set_locale'","app/controllers/application_controller.rb:466:in `set_current_context'","lib/gitlab/metrics/elasticsearch_rack_middleware.rb:16:in `call'","lib/gitlab/middleware/rails_queue_duration.rb:33:in `call'","lib/gitlab/metrics/rack_middleware.rb:16:in `block in call'","lib/gitlab/metrics/web_transaction.rb:21:in `run'","lib/gitlab/metrics/rack_middleware.rb:16:in `call'","lib/gitlab/middleware/speedscope.rb:13:in `call'","lib/gitlab/request_profiler/middleware.rb:17:in `call'","lib/gitlab/jira/middleware.rb:19:in `call'","lib/gitlab/middleware/go.rb:20:in `call'","lib/gitlab/etag_caching/middleware.rb:21:in `call'","lib/gitlab/middleware/multipart.rb:172:in `call'","lib/gitlab/middleware/read_only/controller.rb:50:in `call'","lib/gitlab/middleware/read_only.rb:18:in `call'","lib/gitlab/middleware/same_site_cookies.rb:27:in `call'","lib/gitlab/middleware/handle_malformed_strings.rb:21:in `call'","lib/gitlab/middleware/basic_health_check.rb:25:in `call'","lib/gitlab/middleware/handle_ip_spoof_attack_error.rb:25:in `call'","lib/gitlab/middleware/request_context.rb:21:in `call'","config/initializers/fix_local_cache_middleware.rb:11:in `call'","lib/gitlab/middleware/rack_multipart_tempfile_factory.rb:19:in `call'","lib/gitlab/metrics/requests_rack_middleware.rb:74:in `call'","lib/gitlab/middleware/release_env.rb:12:in `call'"],"db_duration_s":0.23847,"view_duration_s":0.0,"duration_s":73.05203}","ua":"Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0","correlation_id":"01K3Q2F5DXP8Y33B25TW9SSMYM","meta.user":"xxx","meta.project":"xxx/xxx","meta.root_namespace":"storage","meta.caller_id":"ProjectsController#show","meta.remote_ip":"10.17.7.63","meta.feature_category":"projects","meta.client_id":"user/74","redis_calls":21,"redis_duration_s":0.007123,"redis_read_bytes":2997,"redis_write_bytes":2370,"redis_cache_calls":20,"redis_cache_duration_s":0.006591,"redis_cache_read_bytes":2816,"redis_cache_write_bytes":1035,"redis_shared_state_calls":1,"redis_shared_state_duration_s":0.000532,"redis_shared_state_read_bytes":181,"redis_shared_state_write_bytes":1335,"db_count":41,"db_write_count":0,"db_cached_count":10,"cpu_s":2.291446,"mem_objects":394125,"mem_bytes":52397272,"mem_mallocs":198648,"mem_total_bytes":68162272,"queue_duration_s":0.009214,"exception.class":"Rack::Timeout::RequestTimeoutException","exception.message":"Request ran for longer than 60000ms","exception.backtrace":["lib/gitlab/url_blocker.rb:113:in `getaddrinfo'","lib/gitlab/url_blocker.rb:113:in `get_address_info'","lib/gitlab/url_blocker.rb:48:in `validate!'","app/validators/addressable_url_validator.rb:83:in `validate_each'","app/models/badge.rb:43:in `build_rendered_url'","app/models/badge.rb:36:in `rendered_image_url'","app/models/badges/project_badge.rb:15:in `rendered_image_url'","app/views/projects/_home_panel.html.haml:93","app/views/projects/_home_panel.html.haml:89","app/views/projects/_home_panel.html.haml:87","app/views/projects/show.html.haml:14","app/controllers/application_controller.rb:128:in `render'","app/controllers/application_controller.rb:538:in `block in allow_gitaly_ref_name_caching'","lib/gitlab/gitaly_client.rb:341:in `allow_ref_name_caching'","app/controllers/application_controller.rb:537:in `allow_gitaly_ref_name_caching'","app/controllers/application_controller.rb:487:in `set_current_admin'","lib/gitlab/session.rb:11:in `with_session'","app/controllers/application_controller.rb:478:in `set_session_storage'","lib/gitlab/i18n.rb:99:in `with_locale'","lib/gitlab/i18n.rb:105:in `with_user_locale'","app/controllers/application_controller.rb:472:in `set_locale'","app/controllers/application_controller.rb:466:in `set_current_context'","lib/gitlab/metrics/elasticsearch_rack_middleware.rb:16:in `call'","lib/gitlab/middleware/rails_queue_duration.rb:33:in `call'","lib/gitlab/metrics/rack_middleware.rb:16:in `block in call'","lib/gitlab/metrics/web_transaction.rb:21:in `run'","lib/gitlab/metrics/rack_middleware.rb:16:in `call'","lib/gitlab/middleware/speedscope.rb:13:in `call'","lib/gitlab/request_profiler/middleware.rb:17:in `call'","lib/gitlab/jira/middleware.rb:19:in `call'","lib/gitlab/middleware/go.rb:20:in `call'","lib/gitlab/etag_caching/middleware.rb:21:in `call'","lib/gitlab/middleware/multipart.rb:172:in `call'","lib/gitlab/middleware/read_only/controller.rb:50:in `call'","lib/gitlab/middleware/read_only.rb:18:in `call'","lib/gitlab/middleware/same_site_cookies.rb:27:in `call'","lib/gitlab/middleware/handle_malformed_strings.rb:21:in `call'","lib/gitlab/middleware/basic_health_check.rb:25:in `call'","lib/gitlab/middleware/handle_ip_spoof_attack_error.rb:25:in `call'","lib/gitlab/middleware/request_context.rb:21:in `call'","config/initializers/fix_local_cache_middleware.rb:11:in `call'","lib/gitlab/middleware/rack_multipart_tempfile_factory.rb:19:in `call'","lib/gitlab/metrics/requests_rack_middleware.rb:74:in `call'","lib/gitlab/middleware/release_env.rb:12:in `call'"],"db_duration_s":0.23847,"view_duration_s":0.0,"duration_s":73.05203}

问题分析

经过 claude 的分析,发现是 badge 加载失败导致的

以下是完整回答:

根据这个 GitLab 日志,我可以看到问题的核心原因和解决方案:

问题分析

从日志中可以看到关键错误信息:

  1. 超时错误: Rack::Timeout::RequestTimeoutException (Request ran for longer than 60000ms) – 请求超过了60秒限制
  2. 根本原因: 错误发生在项目徽章(badge)渲染过程中,具体是在 lib/gitlab/url_blocker.rb:113:in 'getaddrinfo'
  3. 调用链: 项目页面渲染 → 徽章URL验证 → DNS解析超时

可能的原因

  1. 网络连通性问题: GitLab 尝试验证项目徽章的外部URL时,DNS解析或网络连接超时
  2. 徽章配置问题: 该项目配置了无效或无法访问的徽章URL
  3. 防火墙/代理问题: 网络策略阻止了对外部徽章服务的访问

解决方案

1. 临时解决方案 – 禁用徽章验证

方案无效。

2. 根本解决方案 – 检查项目徽章配置

  1. 通过管理员界面检查
    • 进入该项目的设置页面
    • 查看 “General” → “Badges” 部分
    • 删除或修复无效的徽章URL
  2. 通过数据库直接检查

    按照这个步骤成功解决。

    
    # 进入GitLab控制台
    gitlab-rails console

查找该项目的徽章

project = Project.find_by_full_path(‘xxx/xxx’) project.badges.each { |badge| puts “#{badge.id}: #{badge.link_url} | #{badge.image_url}” }

删除有问题的徽章

Badge.where(id: [有问题的徽章ID]).delete_all


### 3. 网络诊断

检查 GitLab 服务器的网络连通性:

```bash
# 测试DNS解析
nslookup [徽章URL的域名]

# 测试网络连通性
curl -I [完整的徽章URL]

4. 增加调试信息

临时启用详细日志来定位具体的URL:

# 在 /etc/gitlab/gitlab.rb 中
gitlab_rails['log_level'] = 'debug'

建议先尝试解决方案1作为紧急修复,然后通过解决方案2找到并修复具体的徽章配置问题。这样既能快速恢复服务,又能从根本上解决问题。

结论

没想到一个 badge 会导致这么大问题,可能因为版本比较低,不知道新版本有没有解决这个问题。

harbor Chartmuseum helm 图表缓存刷新

harbor 集成 Chartmuseum 实现 helm 图表的存储。

Chartmuseum 对于图表索引缓存的处理有问题,删除、重传 chart 不会刷新索引,需要手动刷新。

TL;DR

操作步骤如下:

  1. 停止 harbor,例如: docker compose stop
  2. 删除 /data/chart_storage/{project}/index-cache.yaml
  3. 删除 /data/redis/*
  4. 启动 harbor,例如: docker compose up -d

记得备份。

Refs

Debian 11 + PVE LACP Mac 冲突问题调查与解决方案

By TL.S

问题背景

在Debian 11 + Proxmox VE环境中,当配置LACP(Link Aggregation Control Protocol)绑定接口时,有一个环境遇到一个令人困惑的问题:相同硬件配置的多台服务器上,所有服务器的同名bond接口都被分配了相同的MAC地址。这种情况会导致严重的网络连接问题,特别是在集群环境中。

根本原因分析

经过深入调查,这个问题的根本原因可以归纳为以下几个方面:

1. systemd/udev MAC 地址生成策略变更

systemd 242 版本开始,引入了新的MACAddressPolicy=persistent策略。该策略的目标是为虚拟网络设备(如bond、bridge、vlan等)生成持久的MAC地址,避免重启后MAC地址变化。

核心变更:systemd通过哈希算法,基于机器的machine-id和接口名称来生成MAC地址,确保在同一台机器上重启后MAC地址保持不变。

Refs: https://github.com/systemd/systemd/pull/11382

2. machine-id 相同导致的问题

关键问题:当多台服务器具有相同的machine-id时,相同的接口名称会生成完全相同的MAC地址。

machine-id 相同的几种情况:

  • 克隆安装:从同一个镜像克隆安装的系统
  • 模板部署:使用相同模板部署的虚拟机或物理机
  • 其他原因:其他原因导致 /etc/machine-id文件内容重复

3. MAC地址分配类型

通过查看系统文件可以确认MAC地址的分配方式:

root@server:~# cat /sys/class/net/bond0/addr_assign_type
3

根据Linux内核文档,addr_assign_type=3表示”set using dev_set_mac_address”,即通过dev_set_mac_address函数设置的MAC地址。

4. 虚拟设备的特殊性

问题主要影响虚拟网络设备,因为这些设备缺少必要的ID_NET_NAME_*属性,udev 无法为它们分配稳定的MAC地址。systemd 的解决方案是使用 接口名称+machine-id 的组合来生成MAC地址。

技术原理深度解析

MAC地址生成算法

systemd使用以下算法生成MAC地址:

  1. 获取/etc/machine-id的内容
  2. 结合接口名称(如bond0、vmbr0等)
  3. 通过哈希算法计算出唯一的MAC地址
  4. 确保生成的MAC地址符合本地管理地址规范

解决方案

方案一:重置machine-id(推荐)

这是最根本的解决方案,适用于所有情况:

# 停止网络服务
systemctl stop networking

# 备份原machine-id(可选)
cp /etc/machine-id /etc/machine-id.backup

# 清空machine-id
echo -n > /etc/machine-id

# 重新生成machine-id
systemd-machine-id-setup

# 验证新的machine-id
cat /etc/machine-id

# 重启系统使配置生效
reboot

注意事项

  • 重置machine-id可能影响其他依赖machine-id的服务
  • 建议在维护窗口期间执行
  • 重启后所有虚拟网络接口的MAC地址都会更新

方案二:手动指定MAC地址

如果不想更改machine-id,可以在网络配置中手动指定MAC地址:

# 编辑网络配置文件
vim /etc/network/interfaces

# 为bond接口指定唯一的MAC地址
auto bond0
iface bond0 inet manual
    bond-slaves ens1f0 ens1f1
    bond-miimon 100
    bond-mode 802.3ad
    bond-xmit-hash-policy layer3+4
    hwaddress ether 02:01:02:03:04:05  # 手动指定MAC地址

# 为bridge接口指定唯一的MAC地址
auto vmbr0
iface vmbr0 inet static
    address 10.10.4.41/24
    gateway 10.10.4.1
    bridge-ports bond0
    bridge-stp off
    bridge-fd 0
    hwaddress ether 02:01:02:03:04:06  # 手动指定MAC地址

方案三:更改MAC地址策略

修改systemd的MAC地址策略来解决MAC冲突问题。首先了解当前策略和可选项:

当前默认策略查看

# 查看当前systemd网络配置
ls -la /etc/systemd/network/
ls -la /lib/systemd/network/

# 查看当前默认策略文件(如果存在)
cat /lib/systemd/network/99-default.link 2>/dev/null || echo "默认策略文件不存在"

# 查看当前接口的MAC策略
networkctl status bond0

MAC地址策略选项说明

systemd支持以下MACAddressPolicy选项:

策略 说明 适用场景
persistent 当前默认策略,基于machine-id和接口名生成稳定MAC 需要MAC地址在重启后保持不变
random 每次启动时生成随机MAC地址 不需要MAC地址稳定性
none 不设置MAC地址,使用驱动程序默认值 物理接口或需要保持原有MAC

当前问题的根源:默认的persistent策略在相同machine-id的服务器上会生成相同的MAC地址。

实施步骤

# 1. 检查是否已有配置文件
echo "检查现有配置..."
if [ -f /etc/systemd/network/99-default.link ]; then
    echo "发现现有配置文件:"
    cat /etc/systemd/network/99-default.link
    echo ""
    echo "是否要备份现有配置?(y/n)"
    read -r response
    if [ "$response" = "y" ]; then
        cp /etc/systemd/network/99-default.link /etc/systemd/network/99-default.link.backup.$(date +%Y%m%d_%H%M%S)
        echo "已备份到: /etc/systemd/network/99-default.link.backup.$(date +%Y%m%d_%H%M%S)"
    fi
else
    echo "未发现现有配置文件,将创建新配置"
fi

# 2. 创建网络配置目录(如果不存在)
mkdir -p /etc/systemd/network

# 3. 根据需求选择策略创建配置文件

# 选项A:使用random策略(推荐用于解决冲突)
cat > /etc/systemd/network/99-default.link << EOF
# MAC地址策略配置
# 创建时间: $(date)
# 目的: 解决LACP MAC地址冲突问题

[Match]
OriginalName=*

[Link]
MACAddressPolicy=random
NamePolicy=keep kernel database onboard slot path
EOF

# 选项B:仅对虚拟接口使用random策略
cat > /etc/systemd/network/99-virtual.link << EOF
# 虚拟接口MAC地址策略配置
# 创建时间: $(date)

[Match]
Kind=bond bridge vlan macvlan veth

[Link]
MACAddressPolicy=random
EOF

# 选项C:禁用MAC地址自动分配
cat > /etc/systemd/network/99-no-mac.link << EOF
# 禁用MAC地址自动分配
# 创建时间: $(date)

[Match]
OriginalName=*

[Link]
MACAddressPolicy=none
EOF

# 4. 验证配置文件语法
echo "验证配置文件..."
systemd-analyze verify /etc/systemd/network/99-default.link

# 5. 应用新策略
echo "应用新的MAC地址策略..."
# 重新加载systemd配置
systemctl daemon-reload

# 重启网络服务(根据系统使用的网络管理器)
if systemctl is-active --quiet systemd-networkd; then
    systemctl restart systemd-networkd
    echo "已重启systemd-networkd"
elif systemctl is-active --quiet networking; then
    systemctl restart networking
    echo "已重启networking服务"
else
    echo "警告:未检测到活跃的网络服务,可能需要手动重启网络或系统"
fi

# 6. 验证策略是否生效
echo "等待网络接口重新初始化..."
sleep 5

echo "验证新的MAC地址策略:"
networkctl status | grep -E "(bond|vmbr|bridge)"

策略选择建议

推荐策略组合

  1. 对于生产环境

    # 仅对虚拟接口使用random策略,物理接口保持默认
    [Match]
    Kind=bond bridge vlan
    
    [Link]
    MACAddressPolicy=random
  2. 对于测试环境

    # 所有接口使用random策略
    [Match]
    OriginalName=*
    
    [Link]
    MACAddressPolicy=random
  3. 对于特定接口

    # 只对特定名称的接口应用
    [Match]
    OriginalName=bond* vmbr*
    
    [Link]
    MACAddressPolicy=random

回滚方案

如果新策略导致问题,可以快速回滚:

# 删除自定义策略文件
rm -f /etc/systemd/network/99-default.link

# 或者恢复备份
if [ -f /etc/systemd/network/99-default.link.backup.* ]; then
    cp /etc/systemd/network/99-default.link.backup.* /etc/systemd/network/99-default.link
fi

# 重启网络服务
systemctl daemon-reload
systemctl restart systemd-networkd

注意事项

  • 影响范围:此方案会影响系统中所有匹配的网络接口
  • 重启要求:配置更改后,需要重启网络服务或重启系统
  • 兼容性:确保你的系统使用systemd-networkd而不是NetworkManager
  • 监控:更改后应监控网络连接是否正常

方案四:运行时动态修改

如果需要立即解决问题而不重启:

# 生成随机MAC地址(注意要符合本地管理地址规范)
NEW_MAC=$(printf '02:00:00:%02x:%02x:%02x\n' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)))

# 停止接口
ip link set dev bond0 down

# 设置新的MAC地址
ip link set dev bond0 address $NEW_MAC

# 启动接口
ip link set dev bond0 up

# 验证更改
ip link show bond0

预防措施

1. 标准化部署流程

在部署新系统时,确保:

# 在系统部署脚本中加入
if [ -f /etc/machine-id ]; then
    echo "重置machine-id..."
    echo -n > /etc/machine-id
    systemd-machine-id-setup
fi

2. 监控和检查

定期检查集群中的MAC地址冲突:

#!/bin/bash
# mac_check.sh - 检查MAC地址冲突

echo "检查集群MAC地址冲突..."
for host in server1 server2 server3; do
    echo "=== $host ==="
    ssh $host "ip link show | grep -E 'bond|vmbr' | grep 'link/ether'"
done

3. 文档化和记录

建立标准的网络配置文档,记录:

  • 每台服务器的machine-id
  • 手动分配的MAC地址范围
  • 网络拓扑和接口映射

影响范围和版本信息

受影响版本

  • Debian 10/11 + systemd >= 242
  • Proxmox VE 6.x/7.x/8.x
  • 其他基于systemd的Linux发行版

特别注意

  • Intel网卡比Broadcom网卡更容易出现此问题
  • 虚拟化环境中的问题更加常见
  • 集群环境影响更严重

总结

Debian 11 + PVE LACP Mac冲突问题的根本原因是systemd的MAC地址生成策略与machine-id机制的相互作用。当多台服务器具有相同的machine-id时,会为相同名称的虚拟网络接口生成相同的MAC地址,导致网络冲突。

References

Claude Code 实用技巧

  1. CLAUDE.md(规则文件)= 冰箱家规 先把“进门换鞋、10点关灯、刀具归位”写清楚,Claude 做任何事之前都要看一遍并遵守。

  2. Task(多任务并行)= 多台家电同时干活 扫地机器人+洗碗机+空调一起开工,Claude 同时跑多个任务,效率翻倍。

  3. /自定义指令(常用操作快捷)= 场景模式 设好“回家模式”:开门回家就自动 开灯→开空调→放音乐。

  4. Hook (事件触发器)= 自动提醒/联动开关 洗衣机结束→手机提醒你晾衣服;门磁感应到你进门→灯自己亮。

  5. Sub Agent(子智能体)= 家里的专业小队 管家、厨师、保洁、物业、维修师傅……每个都有专长,随叫随到

还有个mcp,这个是标配了~ ————

可以按照如下流程来打造“满血版 Claude Code”

先定家规(CLAUDE.md)→ 招专业团队(Sub Agent)→ 设自动触发器(Hook)→ 添加mcp → 做几个一键场景口令(/自定义指令)→ 一点全自动多任务Task同时执行

References

CentOS 7 重置 root 密码

引言

很多遗留系统都采用 CentOS 系统,经常出现忘记密码,故在此记录方法。

以下内容转自 CentOS 7 重置 root 密码

CentOS 7 重置 root 密码

与之前的 CentOS 5、 CentOS 6 不同的是,当忘记 CentOS 7 root 密码,并采用 GRUB2 为启动器时,
将无法通过单用户模式重置 root 密码,下面介绍 CentOS 7 如何重置 root 密码。

  1. 启动系统,并在 GRUB2 启动屏显时,按下 e 键进入编辑模式。
  2. linux16/linux/linuxefi 所在参数行尾添加以下内容: init=/bin/sh
  3. 按 Ctrl + X 启动到 Shell。
  4. 挂载文件系统为可写模式:mount -o remount,rw /
  5. 运行 passwd, 并按提示修改 root 密码。
  6. 运行命令 exec /sbin/init 来正常启动,或者用命令 exec /sbin/reboot 重启

遇到的问题,不确认是 VMware Workstation 有 Bug 还是键盘问题,进行密码重置时,
输入 2 次密码很多次,总是提示密码不匹配,用很简单的密码也提示。
然后采用直接清空 root 密码的方式(/etc/passwd root 用户密码列 x 删除)先登录系统,然后再修改。

Ref

telnet 如何退出

telnet 的退出 分成两种,一中是在telnet命令中,直接输入 quit 或者 q 即可退出。 二种情况是 已经进入了端口中,需要先从端口中退出,然后再退出telnet。

telnet 直接退出

输入 q 或者 quit 即可退出。

telnet 退出端口后退出telnet

  1. 输出 ctrl + ]
  2. q 或者 quit

Ref

kubernetes 的挂载传播(mount propagation)机制

以下内容转载自:kubernetes 的挂载传播(mount propagation)机制

概述

今天在看 kubectl-debug 这个项目的时候,看到其部署文件的 volumeMounuts 中使用了一个 mountPropagation 字段,因为不清楚这个字段的作用,就做了一下了解。mount propagation 背后的东西还是很多的,因此整理了这篇文章,顺便梳理一下知识点。

kubernetes 的 mount propagation 翻译成中文就是挂载传播。挂载传播提供了共享卷挂载的能力,它允许在同一个 Pod,甚至同一个节点内,在多个容器之间共享卷的挂载。

kubernetes 的挂载传播

卷的挂载传播由 Container.volumeMounts 的 mountPropagation 字段控制。它的值有:

  • None: 这种卷挂载将不会收到任何后续由 host 创建的在这个卷上或其子目录上的挂载。同样的,由容器创建的挂载在 host 上也是不可见的。这是默认的模式。这个其实很好理解,就是容器内和 host 的后续挂载完全隔离。
  • HostToContainer: 这种卷挂载将会收到之后所有的由 host 创建在该卷上或其子目录上的挂载。换句话说,如果 host 在卷挂载内挂载的任何内容,在容器中都是可见的。同样,如果任何具有 Bidirectional 的 Pod 挂载传播到该卷挂载上,具有 HostToContainer 的挂载传播都可以看见。整个挂载传播的流程如下:

  • Bidirectional: 这种挂载机制和 HostToContainer 类似。此外,任何在容器中创建的挂载都会传播到 host,然后传播到使用相同卷的所有 Pod 的所有容器。注意:Bidirectional 挂载传播是很危险的。可能会危害到 host 的操作系统。因此只有特权容器在允许使用它。

在了解了这几种挂载传播之后,我们可以做一些实验来验证一下,首先验证的是 None 的挂载传播类型,我们创建一个 nginx 的Pod:

apiVersion: v1
kind: Pod
metadata:
    name: mount-a
    namespace: default
    label:
      app: mount
spec:
    containers:
    - name: main
      image: nginx:latest
      volumeMounts:
      - name: testmount
        mountPath: /home
        mountPropagation: None
    volumes:
    - name: testmount
      hostPath:
        path: /mnt/

YAML

然后我们分别向 host 的 /mnt 和容器的 /home 下挂载目录并查看容器和 host 的情况:

容器中:

$ kubectl exec -it mount-a sh
$ cd /home
$ ls 
sda1

Bash

host 上:

$ cd /mnt
$ ls 
sda1

Bash

然后在 host 上创建挂载:

$ mkdir /mnt/none
$ sudo mount --bind /var /mnt/none
$ ls none
cache  empty  lib  lock  log  run  spool  tmp

Bash

这个时候,我们再看容器中的文件:

$ ls none
# 无输出

Bash

这说明 host 上在该卷下的挂载并不会改变容器中的文件。接下来我们可以在容器中按照上面的方案来验证容器中的挂载也不会影响 host 中的目录视图。这里就不展示了。接下来看一下 HostToContainer 的挂载传播,我们将上面的 Pod 的 mountPropagation 字段改成 HostToContainer,然后先取消 host 上的挂载:

sudo umoint /mnt/none

然后重新创建 Pod,和上面一样,在 host 上创建挂载,查看容器中的挂载情况:

$ ls none
cache  empty  lib  lock  log  run  spool  tmp

Bash

host 上的挂载因为 HostToContainer 机制传播到了容器中。我们继续看最后一种 Bidirectional 机制。这次我们要创建两个 Pod: mount-a, mount-b,并把 mountPropagation 字段改成 Bidirectional。注意,因为 Bidirectional 是危险的,所以只有特权容器才可以使用。因此这里还需要把容器改成特权模式,最后在 mount-a 中的容器执行挂载,验证挂载是否传播到 host 和 mount-b 的容器中。

apiVersion: v1
kind: Pod
metadata:
    name: mount-a
    namespace: default
    labels:
      app: mount
spec:
    containers:
    - name: main
      image: nginx:latest
      securityContext:
        privileged: true
      volumeMounts:
      - name: testmount
        mountPath: /home
        mountPropagation: Bidirectional
    volumes:
    - name: testmount
      hostPath:
        path: /mnt/
---
apiVersion: v1
kind: Pod
metadata:
    name: mount-b
    namespace: default
    labels:
      app: mount
spec:
    containers:
    - name: main
      image: nginx:latest
      securityContext:
        privileged: true
      volumeMounts:
      - name: testmount
        mountPath: /home
        mountPropagation: Bidirectional
    volumes:
    - name: testmount
      hostPath:
        path: /mnt/

YAML

然后进入 mount-a,创建挂载:

$ kubectl exec -it mount-a sh
$ su
$ mount --bind /var /home/none
$ ls /home/none
backups  lib    lock  mail  run    tmp
cache    local  log   opt   spool

Bash

这时候查看 host 下的 /mnt/none:

$ ls /mnt/none
backups  lib    lock  mail  run    tmp
cache    local  log   opt   spool

Bash

可以发现,容器中的挂载传播到了 host 上。这时候再查看 mount-b 中的容器。

$ ls /home/none
backups  lib    lock  mail  run    tmp
cache    local  log   opt   spool

Bash

挂载也传播到了 mount-b 的容器中。

linux mount 的几种类型

上面分析了 kubernetes 的挂载传播机制,在 linux mount 中,也有类似的概念。mount 分为下面几种:

  • shared mount: 相当于上面所说的 Bidirectional 的挂载传播
  • slave mount: 每个 slave mount 都有一个 shared master mount,挂载传播只能从 master -> slave,等同于上面的 HostToContainer, host 是 master,container 是 slave。
  • private mount: 很明显,private 就是相当于 None,挂载不会向任何一方传播。
  • unbindable mount:unbindable mount 其实就是 unbindable private mount,也就是不允许使用 --bind 的挂载。

mount namespace 的机制

kubernetes 的挂载传播不是其本身实现的,也不是 docker 之类的容器运行时提供的。这是由容器化技术的基础:linux namespace 提供的,linux namespace 当前共有 6 种:

  • cgroup namespace: 隔离 cgroup 根目录
  • pid namespace: 隔离进程 id
  • ipc namespace: 隔离 System V IPC, POSIX message queues
  • uts namespace: 隔离 Hostname 和 NIS domain name
  • user namespace: 隔离用户和用户组 ID
  • mount namespace: 隔离挂载点
  • network namespace: 隔离网络设备,网络栈,端口等

其中,mount namespace 是这篇文章的重点。我们可以通过 clone 调用来看看 mount namespace 的使用:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024*1024)
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    NULL
};

int container_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());
    mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL);
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}
int main()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    /* 启用Mount Namespace - 增加CLONE_NEWNS参数 */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}

C

编译运行:

$ gcc main.c -o mount
$ sudo ./mount

Bash

然后尝试挂载,来验证挂载 MS_PRIVATE 的挂载传播问题。MS_PRIVATE 下 namespace 内和 host 应该是隔离的。MS_PRIVATE 还可以替换成 MS_UNBINDABLEMS_SLAVEMS_SHARED

关于更多的 namespace 的资料,建议看这两篇文章:

参考资料

harbor 替换 ssl 证书

最近内网 harbor 经常迁移,迁移到新地址后 ssl 证书需针对新的地址签发。(当然如果你直接使用 http 就不会有这个烦恼,至于为什么i不直接使用 http 就不多说了)。

这里记录一下签发新的 ssl 证书并迁移的命令,方便后面使用。

# 下面以替换为 10.1.40.39 为例
# 若存在残留证书则删除
$ rm 10.1.40.39.*  
# 生成新的私钥
$ openssl genrsa -out 10.1.40.39.key 4096
# 生成新的证书签名请求(CSR)
$ openssl req -sha512 -new -subj "/C=CN/ST=Guangdong/L=Guangzhou/O=Wuzhou/OU=RD/CN=10.1.40.39" -key 10.1.40.39.key -out 10.1.40.39.csr
# 编辑 `x509 v3` 扩展文件,增加新的地址,如
# +IP.3 = 10.1.40.39
$ vi v3.ext
# 生成新的证书
$ openssl x509 -req -sha512 -days 3650     -extfile v3.ext     -CA ca.crt -CAkey ca.key -CAcreateserial -in 10.1.40.39.csr -out  10.1.40.39.crt
# 转换证书格式供 其他程序 使用
$ openssl x509 -inform PEM -in 10.1.40.39.crt -out 10.1.40.39.cert      
$ openssl x509 -in 10.1.40.39.crt -text -noout | grep -A 10 "Subject Alternative Name"
$ openssl x509 -in 10.1.40.39.cert -text -noout | grep -A 10 "Subject Alternative Name"
# 替换 hostname 和 https 配置
$ vim harbor.yml
# 重启 harbor 加载新证书
$ ./install.sh --with-notary --with-trivy --with-chartmuseum

# Docker 客户端仅需在新的地址下存放 CA证书 即可
$ ls /etc/docker/certs.d/10.1.40.39/ca.crt