k8s / client
PHP 的 Kubernetes 客户端。
Requires
- php: >=7.2
- ext-json: *
- doctrine/annotations: ^1.0
- k8s/api: *
- k8s/core: ^1.3
- klkvsk/json-decode-stream: ^1.0
- php-http/discovery: ^1.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/simple-cache: ^1.0
- symfony/yaml: >=3.4
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.0
- k8s/http-symfony: ^1.0
- k8s/ws-ratchet: ^1.0
- mockery/mockery: ~1.3.0
- php-http/httplug: ^2.0
- phpstan/phpstan: ^0.12
- phpunit/phpunit: ^8.5
Suggests
- k8s/http-guzzle: Guzzle based HttpClient for k8s client auto-configuration.
- k8s/http-symfony: Symfony based HttpClient for k8s client auto-configuration.
- k8s/ws-ratchet: Ratchet based websocket adapter for websocket based API requests.
- k8s/ws-swoole: Swoole based websocket adapter for websocket based API requests.
- symfony/cache: For a PSR-16 implementation to cache Kubernetes model metadata information.
README
k8s-client 是 PHP 的 Kubernetes API 客户端。
- HTTP 客户端无关(支持任何 PSR-18 兼容的 HTTP 客户端)
- 支持所有主要的 API 操作(读取、监视、列表、补丁、删除、执行、附加、日志、端口转发、代理等)
- 支持 Kubernetes API 中的所有类型(通过自动生成的 Kind 模型,带有注释和类型提示)。
- 支持可插拔的 WebSocket 适配器(用于在 pod 中执行命令、附加、端口转发等)
Kind 模型每晚自动生成 Kubernetes API 的最后 10 个版本。
安装
使用 composer 安装
# Install the base client composer require k8s/client # Plan on using the Symfony HttpClient? Install the auto-configuration helper for it. composer require k8s/http-symfony # Plan on needing to use things like executing commands, port-forwarding? # Install a websocket adapter. composer require k8s/ws-ratchet
此库需要一个 PSR-18 兼容的 HTTP 客户端,例如 Guzzle 或 Symfony 的 HttpClient。它还可以提供一个 PSR-16 兼容的 Simple Cache 实现 以帮助加快库的速度。
使用特定的 Kubernetes API 版本
每个 Kubernetes 版本可能有不同的资源和操作。如果您需要特定的版本,则可以要求使用所需的 k8s/api
库的版本。该库包含所有由本库消耗的特定 API 版本和模型。
例如,要使用 API 版本 1.18
composer require k8s/api:"~1.18.0"
注意:k8s/api
的版本并不完全反映 Kubernetes API 的版本。Kubernetes 的补丁版本可能与 k8s/api
的补丁版本不同。
安装 WebSocket 适配器
某些 Kubernetes API 端点(例如 exec,在容器中运行命令)需要 WebSocket 来通信。如果您需要此支持,请安装以下适配器之一
-
基于 ReactPHP 的 WebSocket 适配器(https://github.com/k8s-client/ws-ratchet)
composer require k8s/ws-ratchet
-
基于 Swoole 的 WebSocket 适配器(https://github.com/k8s-client/ws-swoole)
composer require k8s/ws-swoole
有关更多信息,请参阅每个库的 README。
自动构建客户端
构建客户端的最简单方法是使用预定义的 KubeConfig
use K8s\Client\K8sFactory; # Attempt to load the default kubeconfig file: $k8s = (new K8sFactory())->loadFromKubeConfig(); # Attempt to load a specific kubeconfig file from a full file path: $k8s = (new K8sFactory())->loadFromKubeConfigFile('/my/special/.kube/config'); # Load a kubeconfig from string kubeconfig data: $k8s = (new K8sFactory())->loadFromKubeConfigData($kubeConfigData);
注意:这需要使用 HttpClient 工厂辅助工具。安装以下包之一
k8s/http-symfony
(基于 Symfony 的 HttpClient)k8s/http-guzzle
(基于 Guzzle 的 HttpClient)
手动构建客户端
使用您需要的选项构建客户端
use K8s\Client\K8s; use K8s\Client\Options; # Supply the base path to the Kubernetes API endpoint: $options = new Options('https://127.0.0.1:8443'); # To use an API token for authentication, set it in the options: $options->setToken('some-secret-token-value-goes-here'); $k8s = new K8s($options);
注意:如果您需要进行基于证书的认证,请检查您使用的 HttpClient 的选项。还要检查您使用的 WebSocket 适配器的配置。
示例
列出所有 Pod
use K8s\Api\Model\Api\Core\v1\Pod; use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); /** @var Pod $pod */ foreach ($k8s->listAll(Pod::class) as $pod) { echo sprintf( "%s\t%s\t%s", $pod->getPodIP(), $pod->getNamespace(), $pod->getName() ) . PHP_EOL; }
监视命名空间中的所有 Deployment
use K8s\Api\Model\Api\Apps\v1\Deployment; use K8s\Api\Model\ApiMachinery\Apis\Meta\v1\WatchEvent; use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); $count = 0; # This will watch all deployments in the default namespace. # Change the namespace either in the Options above or as a parameter to the watchNamespaced method below. $k8s->watchNamespaced(function (WatchEvent $event) use (&$count) { $count++; /** @var Deployment $object */ $object = $event->getObject(); echo sprintf( "%s\t%s\t%s\t%s", $event->getType(), $object->getName(), $object->getReplicas(), implode(',', (array)$object->getLabels()) ) . PHP_EOL; # Return false if some condition is met to stop watching. if ($count >= 5) { return false; } }, Deployment::class);
创建 Pod
使用模型类
use K8s\Api\Model\Api\Core\v1\Container; use K8s\Api\Model\Api\Core\v1\Pod; use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); # Create a pod with the name "web" using the nginx:latest image... $pod = new Pod( 'web', [new Container('web', 'nginx:latest')] ); # Create will return the updated Pod object after creation in this instance... $pod = $k8s->create($pod); var_dump($pod);
使用数组数据
use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); # Create a pod with the name "web" using the nginx:latest image... # Create will return the updated Pod object after creation in this instance... $pod = $k8s->create($k8s->newKind([ 'apiVersion' => 'v1', 'kind' => 'Pod', 'metadata' => [ 'name' => 'web', ], 'spec' => [ 'containers' => [ [ 'image' => 'nginx:latest', 'name' => 'web', ], ] ], ])); var_dump($pod);
创建 Deployment
使用模型类
use K8s\Api\Model\Api\Apps\v1\Deployment; use K8s\Api\Model\ApiMachinery\Apis\Meta\v1\LabelSelector; use K8s\Api\Model\Api\Core\v1\Container; use K8s\Api\Model\Api\Core\v1\PodTemplateSpec; use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); # All deployments need a "template" that describes the Pod spec $template = new PodTemplateSpec( 'frontend', [new Container('frontend', 'nginx:latest')] ); # The template must have a label that matches the label selector below $template->setLabels(['app' => 'web']); # Create a deployment called "frontend" with the given template. $deployment = new Deployment( 'frontend', new LabelSelector([], ['app' => 'web']), $template ); $result = $k8s->create($deployment); # Create for a deployment will return a Status object for the creation var_dump($result);
使用数组数据
use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); # Create a deployment with the given array data matching what you want. $result = $k8s->create($k8s->newKind([ 'apiVersion' => 'apps/v1', 'kind' => 'Deployment', 'metadata' => [ 'name' => 'frontend', 'labels' => [ 'app' => 'web', ] ], 'spec' => [ 'selector' => [ 'matchLabels' => [ 'app' => 'web', ] ], 'template' => [ 'metadata' => [ 'labels' => [ 'app' => 'web', ] ], 'spec' => [ 'containers' => [ [ 'image' => 'nginx:latest', 'name' => 'frontend', ], ], ], ], ], ])); # Create for a deployment will return a Status object for the creation var_dump($result);
代理HTTP请求到Pod
代理方法向Pod、服务或节点的路径发送HTTP请求。它不对您想要发送的HTTP请求类型做任何假设,因此它接受标准的PSR-7 RequestInterface并返回ResponseInterface。
use Http\Discovery\Psr17FactoryDiscovery; use K8s\Api\Model\Api\Core\v1\Pod; use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); # Get the pod you want to proxy to first $pod = $k8s->read('web', Pod::class); # Create the HTTP request you'd like to send to it $requestFactory = Psr17FactoryDiscovery::findRequestFactory(); $request = $requestFactory->createRequest('GET', '/'); # Send the request to proxy, dump the results # The result will be the raw PSR-7 ResultInterface class. $result = $k8s->proxy($pod, $request); echo (string)$result->getBody().PHP_EOL;
获取 Pod 的日志
use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); # Read logs from a pod called "web". # Also append all log entries with a timestamp (ISO8601) $log = $k8s->logs('web') ->withTimestamps() ->read(); var_dump($log);
跟随 Pod 的日志
use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); $count = 0; # Follow logs from a pod called "web". # Also append all log entries with a timestamp (ISO8601) $k8s->logs('web') ->withTimestamps() ->follow(function (string $log) use (&$count) { $count++; var_dump($log); # Return false at any point to stop following the logs. if ($count >= 5) { return false; } });
在Pod容器中执行命令
use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); # Print the result of "whoami". $k8s->exec('web', '/usr/bin/whoami') ->useStdout() ->run(function (string $channel, string $data) { echo sprintf( '%s => %s', $channel, $data ) . PHP_EOL; });
连接到Pod容器中运行的进程
use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); # Attaches to the main running process of the container in the Pod $k8s->attach('my-pod') # You must specify at least one of useStdout(), useStderr(), useStdin() ->useStdout() # Prints out any STDOUT from the main running process # Can also pass it an instance of ContainerExecInterface ->run(function (string $channel, string $data) { echo sprintf( "%s => %s", $channel, $data ) . PHP_EOL; });
修补 Deployment
use K8s\Api\Model\Api\Apps\v1\Deployment; use K8s\Client\Patch\JsonPatch; use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); $patch = new JsonPatch(); # Since labels are an array, this actually replaces existing labels $patch->add('/metadata/labels', ['app' => 'web']); # Replaces the current replica value with 2 $patch->replace('/spec/replicas', 2); # We first need to read the deployment we want to patch. $deployment = $k8s->read('frontend', Deployment::class); # Now we patch the deployment using the patch object. The returned value will be the updated deployment. $deployment = $k8s->patch($deployment, $patch); echo sprintf( 'Replicas: %s, Labels: %s', $deployment->getReplicas(), implode(',', $deployment->getLabels()) ) . PHP_EOL;
将文件上传到 Pod
use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); $k8s->uploader('my-pod') # Add files from paths. # The first argument is the source location, the second is the destination for it on the container. ->addFile('/path/to/local/file.txt', '/tmp/file.txt') # Add files from string data. # The first argument is the destination path on the container. The second is the file contents as a string. ->addFileFromString('/tmp/hi.txt', 'Oh, hi Mark.') # This actually initiates the upload process. ->upload();
从 Pod 下载文件
use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); $archive = $k8s->downloader('my-pod') # Optionally choose to compress the downloaded files (gzip -- tar.gz) ->compress() # The file(s) or directory to download. Can be an array of files, or just a single directory or file. ->from('/etc') # If you don't specify to() it will download to a temp file. ->to(__DIR__ . '/' . 'podFiles.tar.gz') # Initiate the download process. ->download(); # The full path to the downloaded files archive.. echo (string)$archive . PHP_EOL; # Extract the downloaded files to a directory called "podFiles" in the current directory.. mkdir(__DIR__ . '/podFiles'); $archive->extractTo(__DIR__ . '/podFiles');
从 Pod 进行端口转发
注意:以下示例假设存在一个名为portforward-example
的Pod,该Pod在80端口上提供HTTP服务(例如基于nginx的镜像)。
创建一个对端口转发事件做出响应的类
namespace App; use K8s\Client\Websocket\Contract\PortChannelInterface; use K8s\Client\Websocket\Contract\PortForwardInterface; use K8s\Client\Websocket\PortChannels; class PortForwarder implements PortForwardInterface { /** * @var PortChannels */ private $portChannels; /** * @inheritDoc */ public function onInitialize(PortChannels $portChannels) : void { $this->portChannels = $portChannels; # On initialize, send this HTTP request across. # Due to "Connection: close" HTTP instruction, the websocket will close after the response is received. # In a more realistic situation, you'd probably want to keep this open, and react in the onDataReceived method. $data = "GET / HTTP/1.1\r\n"; $data .= "Host: 127.0.0.1\r\n"; $data .= "Connection: close\r\n"; $data .= "Accept: */*\r\n"; $data .= "\r\n"; $this->portChannels->writeToPort(80, $data); } /** * @inheritDoc */ public function onDataReceived(string $data, PortChannelInterface $portChannel) : void { echo sprintf( 'Received data on port %s:', $portChannel->getPortNumber() ) . PHP_EOL; echo $data . PHP_EOL; } /** * @inheritDoc */ public function onErrorReceived(string $data, PortChannelInterface $portChannel) : void { echo sprintf( 'Received error on port %s: %s', $portChannel->getPortNumber(), $data ) . PHP_EOL; } /** * @inheritDoc */ public function onClose() : void { # Do something here to clean-up resources when the connection is closed... } }
将上述类用作端口转发过程的处理器
use App\PortForwarder; use K8s\Client\K8sFactory; $k8s = (new K8sFactory())->loadFromKubeConfig(); $handler = new PortForwarder(); # Assuming a Pod with a basic HTTP port 80 exposed... $k8s->portforward('portforward-example', 80) ->start($handler);