kubeconfig = $config['kubeconfig']; $this->proxy = $config['proxy'] == 1; } public function check() { if (empty($this->kubeconfig)) throw new Exception('Kubeconfig不能为空'); $this->verify(); } public function deploy($fullchain, $privatekey, $config, &$info) { $namespace = $config['namespace']; $secretName = $config['secret_name']; if (empty($namespace)) throw new Exception('命名空间不能为空'); if (empty($secretName)) throw new Exception('Secret名称不能为空'); $this->parse(); $secretPayload = [ 'apiVersion' => 'v1', 'kind' => 'Secret', 'metadata' => ['name' => $secretName, 'namespace' => $namespace], 'type' => 'kubernetes.io/tls', 'data' => [ 'tls.crt' => base64_encode($config['fullchain']), 'tls.key' => base64_encode($config['privatekey']), ], ]; $secretUrl = '/api/v1/namespaces/' . $namespace . '/secrets/' . $secretName; list($sCode, $sBody, $sErr) = $this->k8s_request('GET', $secretUrl); if ($sCode === 404) { $createUrl = '/api/v1/namespaces/' . $namespace . '/secrets'; $this->log('Secret:' . $secretName . ' 不存在,正在创建...'); list($cCode, $cBody, $cErr) = $this->k8s_request('POST', $createUrl, json_encode($secretPayload)); if ($cCode < 200 || $cCode >= 300) throw new Exception("创建Secret失败 (HTTP $cCode): $cBody | $cErr"); $this->log('Secret:' . $namespace . ' 创建成功'); } elseif ($sCode >= 200 && $sCode < 300) { $this->log('Secret:' . $secretName . ' 已存在,正在更新...'); $patch = ['data' => $secretPayload['data'], 'type' => 'kubernetes.io/tls']; list($pCode, $pBody, $pErr) = $this->k8s_request('PATCH', $secretUrl, json_encode($patch)); if ($pCode < 200 || $pCode >= 300) throw new Exception("更新Secret失败 (HTTP $pCode): $pBody | $pErr"); $this->log('Secret:' . $secretName . ' 更新成功'); } else { throw new Exception("获取Secret失败 (HTTP $sCode): $sBody | $sErr"); } // Bind Secret to specified Ingresses (merge spec.tls & hosts) ---- if (!empty($config['ingresses'])) { $ingressUrl = '/apis/networking.k8s.io/v1/namespaces/' . $namespace . '/ingresses'; foreach (explode(',', $config['ingresses']) as $ingName) { list($gCode, $gBody, $gErr) = $this->k8s_request('GET', $ingressUrl . '/' . $ingName); if ($gCode < 200 || $gCode >= 300) throw new Exception("获取Ingress '$ingName' 失败 (HTTP $gCode): $gBody | $gErr"); $ing = json_decode($gBody, true); if (!$ing) throw new Exception("解析Ingress '$ingName' JSON失败: $gBody"); // collect hosts from spec.rules $hosts = []; foreach (($ing['spec']['rules'] ?? []) as $rule) { if (!empty($rule['host'])) $hosts[] = $rule['host']; } $hosts = array_values(array_unique($hosts)); // merge/ensure spec.tls entry $tls = $ing['spec']['tls'] ?? []; $found = false; foreach ($tls as &$entry) { if (($entry['secretName'] ?? '') === $secretName) { $found = true; $existingHosts = $entry['hosts'] ?? []; $entry['hosts'] = array_values(array_unique(array_merge($existingHosts, $hosts))); } } unset($entry); if (!$found) { $tls[] = ['secretName' => $secretName, 'hosts' => $hosts]; } $patch = ['spec' => ['tls' => $tls]]; list($iCode, $iBody, $iErr) = $this->k8s_request('PATCH', $ingressUrl . '/' . $ingName, json_encode($patch, JSON_UNESCAPED_SLASHES)); if ($iCode < 200 || $iCode >= 300) throw new Exception("更新Ingress '$ingName' 失败 (HTTP $iCode): $iBody | $iErr"); $this->log("Ingress '$ingName' 更新TLS成功"); } } } private function parse() { $kcfg = Yaml::parse($this->kubeconfig); if (!$kcfg) throw new Exception('Kubeconfig格式错误'); $curr = $kcfg['current-context'] ?? null; if (!$curr) throw new Exception('Kubeconfig缺少current-context'); $contexts = $this->index_by_name($kcfg['contexts'] ?? []); $clusters = $this->index_by_name($kcfg['clusters'] ?? []); $users = $this->index_by_name($kcfg['users'] ?? []); $ctx = $contexts[$curr] ?? null; if (!$ctx) throw new Exception("Kubeconfig中找不到current-context: $curr"); $clusterName = $ctx['context']['cluster'] ?? null; $userName = $ctx['context']['user'] ?? null; if (!$clusterName || !$userName) throw new Exception("Kubeconfig中context缺少cluster或user: $curr"); $cluster = $clusters[$clusterName] ?? null; $user = $users[$userName] ?? null; if (!$cluster) throw new Exception("Kubeconfig中找不到cluster: $clusterName"); if (!$user) throw new Exception("Kubeconfig中找不到user: $userName"); $this->server = $cluster['cluster']['server'] ?? null; if (!$this->server) throw new Exception("Kubeconfig中找不到cluster.server"); $this->server = rtrim($this->server, '/'); $this->bearerToken = $user['user']['token'] ?? ($user['user']['auth-provider']['config']['access-token'] ?? null); $clientCertFile = $clientKeyFile = null; if (!empty($user['user']['client-certificate-data']) && !empty($user['user']['client-key-data'])) { $clientCertFile = tempnam(sys_get_temp_dir(), 'kcc_'); $clientKeyFile = tempnam(sys_get_temp_dir(), 'kck_'); file_put_contents($clientCertFile, base64_decode($user['user']['client-certificate-data'])); file_put_contents($clientKeyFile, base64_decode($user['user']['client-key-data'])); } elseif (!empty($user['user']['client-certificate']) && !empty($user['user']['client-key'])) { $clientCertFile = $user['user']['client-certificate']; $clientKeyFile = $user['user']['client-key']; } $this->tls = ['cert' => $clientCertFile, 'key' => $clientKeyFile]; } private function verify() { $this->parse(); list($vCode, $vBody, $vErr) = $this->k8s_request('GET', '/version'); if ($vErr) throw new Exception("连接Kubernetes API服务器失败: $vErr"); if ($vCode != 200) throw new Exception("连接Kubernetes API服务器失败: HTTP $vCode $vBody"); } private function k8s_request($method, $path, $body = null) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->server . $path); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 30); $headers = ['Accept: application/json']; if ($this->bearerToken) $headers[] = 'Authorization: Bearer ' . $this->bearerToken; if ($body !== null) $headers[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, $body); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); if (!empty($this->tls['cert']) && !empty($this->tls['key'])) { curl_setopt($ch, CURLOPT_SSLCERT, $this->tls['cert']); curl_setopt($ch, CURLOPT_SSLKEY, $this->tls['key']); } $resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $err = curl_error($ch); curl_close($ch); return [$code, $resp, $err]; } private function index_by_name($arr) { $out = []; foreach ($arr as $item) { if (isset($item['name'])) $out[$item['name']] = $item; } return $out; } public function setLogger($func) { $this->logger = $func; } private function log($txt) { if ($this->logger) { call_user_func($this->logger, $txt); } } }