k8s/client

PHP 的 Kubernetes 客户端。

1.7.0 2021-06-20 17:01 UTC

This package is auto-updated.

Last update: 2024-08-28 20:29:53 UTC


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 来通信。如果您需要此支持,请安装以下适配器之一

有关更多信息,请参阅每个库的 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);