通过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 exec和kubectl 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




