You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

通过Kubernetes API或Ruby Kubeclient在Pod间读写/复制文件时遭遇400 Bad Request错误的技术问询

解决Kubernetes API执行exec/cp时的"Upgrade request required"错误

你遇到的400 Bad Request错误核心原因很明确:Kubernetes的pods/exec API依赖WebSocket协议,而你用的curl和普通RestClient请求都是标准HTTP请求,没有处理HTTP Upgrade到WebSocket的握手流程,所以会返回"Upgrade request required"。

下面分两部分给出Ruby环境下的解决方案,分别对应kubectl execkubectl cp的功能:

一、实现等效于kubectl exec的功能

要调用K8s的exec API,你需要用支持WebSocket的库来处理连接。Ruby中可以用faye-websocket这个库来实现,步骤如下:

1. 安装依赖

首先添加gem到你的Gemfile:

gem 'faye-websocket'
gem 'openssl'

2. 示例代码

这段代码会连接到目标Pod的exec API,执行cat /tmp/myfile.txt并输出结果:

require 'faye/websocket'
require 'openssl'
require 'net/http'
require 'uri'
require 'securerandom'

# 从serviceaccount获取凭证
token = File.read('/var/run/secrets/kubernetes.io/serviceaccount/token').strip
ca_cert = OpenSSL::X509::Certificate.new(File.read('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'))

# 构造WebSocket URL(注意协议是wss,对应HTTPS)
namespace = 'mynamespace'
pod_name = 'mypod'
command = ['cat', '/tmp/myfile.txt']
query_params = {
  command: command,
  stdout: 'true',
  stderr: 'true',
  stdin: 'false'
}.to_query
ws_url = "wss://kubernetes.default.svc/api/v1/namespaces/#{namespace}/pods/#{pod_name}/exec?#{query_params}"

# 创建WebSocket客户端
uri = URI.parse(ws_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.cert_store = OpenSSL::X509::Store.new
http.cert_store.add_cert(ca_cert)

# 发起WebSocket握手请求
headers = {
  'Authorization' => "Bearer #{token}",
  'Upgrade' => 'websocket',
  'Connection' => 'Upgrade',
  'Sec-WebSocket-Key' => Base64.strict_encode64(SecureRandom.bytes(16)),
  'Sec-WebSocket-Version' => '13'
}

http.start do
  response = http.send_request('GET', uri.request_uri, nil, headers)
  if response.is_a?(Net::HTTPUpgrade)
    ws = Faye::WebSocket.new(response.socket, nil, headers)

    # 处理标准输出/错误输出
    ws.on :message do |event|
      data = event.data
      # K8s exec的WebSocket消息会用帧类型区分流:
      # 帧的第一个字节是0=stdin, 1=stdout, 2=stderr
      case data.getbyte(0)
      when 1
        puts "#{data[1..-1]}"
      when 2
        warn "错误输出: #{data[1..-1]}"
      end
    end

    ws.on :close do |event|
      puts "连接关闭: #{event.code} - #{event.reason}"
      ws = nil
    end

    # 保持运行直到命令执行完成
    loop { EM.run } if EM.reactor_running?.nil?
  else
    puts "握手失败: #{response.code} #{response.message}"
  end
end

二、实现等效于kubectl cp的功能

kubectl cp本质上是通过exec调用tar命令来传输文件:

  • 从Pod复制到本地:在Pod内执行tar cf - /path/to/target,接收tar流并解压到本地
  • 从本地复制到Pod:本地打包文件为tar流,发送到Pod执行tar xf - -C /target/path

下面是从Pod复制文件到本地的示例代码(需要安装minitar gem):

require 'faye/websocket'
require 'openssl'
require 'net/http'
require 'uri'
require 'securerandom'
require 'archive/tar/minitar'

# 凭证获取同之前的代码
token = File.read('/var/run/secrets/kubernetes.io/serviceaccount/token').strip
ca_cert = OpenSSL::X509::Certificate.new(File.read('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'))

namespace = 'mynamespace'
pod_name = 'mypod'
remote_path = '/tmp/myfile.txt'
local_path = './myfile.txt'

# 构造执行tar命令的WebSocket请求
query_params = {
  command: ['tar', 'cf', '-', remote_path],
  stdout: 'true',
  stderr: 'true',
  stdin: 'false'
}.to_query
ws_url = "wss://kubernetes.default.svc/api/v1/namespaces/#{namespace}/pods/#{pod_name}/exec?#{query_params}"

uri = URI.parse(ws_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.cert_store = OpenSSL::X509::Store.new
http.cert_store.add_cert(ca_cert)

tar_data = StringIO.new

http.start do
  response = http.send_request('GET', uri.request_uri, nil, {
    'Authorization' => "Bearer #{token}",
    'Upgrade' => 'websocket',
    'Connection' => 'Upgrade',
    'Sec-WebSocket-Key' => Base64.strict_encode64(SecureRandom.bytes(16)),
    'Sec-WebSocket-Version' => '13'
  })

  if response.is_a?(Net::HTTPUpgrade)
    ws = Faye::WebSocket.new(response.socket, nil, headers)

    ws.on :message do |event|
      data = event.data
      # 只收集stdout的内容(第一个字节为1)
      if data.getbyte(0) == 1
        tar_data.write(data[1..-1])
      elsif data.getbyte(0) == 2
        warn "错误输出: #{data[1..-1]}"
      end
    end

    ws.on :close do |event|
      puts "连接关闭,开始提取文件"
      tar_data.rewind
      # 从tar流中提取目标文件并保存到本地
      Archive::Tar::Minitar.open(tar_data) do |tar|
        entry = tar.next_entry
        File.write(local_path, entry.read) if entry.name == remote_path.split('/').last
      end
      ws = nil
    end

    loop { EM.run } if EM.reactor_running?.nil?
  else
    puts "握手失败: #{response.code} #{response.message}"
  end
end

关于Kubeclient的说明

Kubeclient Ruby库主要封装了Kubernetes的REST API接口,对于需要WebSocket的exec/attach这类交互式操作,目前没有直接的封装方法,所以需要手动通过WebSocket库来实现上述逻辑。

另外检查你的RBAC配置,pods/exec的verbs包含create是正确的(因为exec API实际是POST请求,对应create verb),你的配置没问题。

内容的提问来源于stack exchange,提问作者Akhilesh Joshi

火山引擎 最新活动