Skip to content

Commit

Permalink
Merge pull request #35 from renoki-co/feature/scaling
Browse files Browse the repository at this point in the history
[feature] Scaling for StatefulSet & Deployment
  • Loading branch information
rennokki authored Oct 31, 2020
2 parents 8981324 + a86c087 commit 73caae1
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 5 deletions.
18 changes: 18 additions & 0 deletions docs/kinds/Deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,24 @@ foreach ($dep->getPods() as $pod) {
}
```

### Scaling

The Scaling API is available via a `K8sScale` resource:

```php
$scaler = $dep->scaler();

$scaler->setReplicas(3)->update(); // autoscale the Deployment to 3 replicas
```

Shorthand, you can use `scale()` directly from the Deployment

```php
$scaler = $dep->scale(3);

$pods = $dep->getPods(); // Expecting 3 pods
```

### Deployment Status

The Status API is available to be accessed for fresh instances:
Expand Down
18 changes: 18 additions & 0 deletions docs/kinds/StatefulSet.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,24 @@ foreach ($sts->getPods() as $pod) {
}
```

### Scaling

The Scaling API is available via a `K8sScale` resource:

```php
$scaler = $sts->scaler();

$scaler->setReplicas(3)->update(); // autoscale the StatefulSet to 3 replicas
```

Shorthand, you can use `scale()` directly from the StatefulSet

```php
$scaler = $sts->scale(3);

$pods = $sts->getPods(); // Expecting 3 pods
```

### StatefulSet Status

The Status API is available to be accessed for fresh instances:
Expand Down
13 changes: 13 additions & 0 deletions src/Contracts/Scalable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace RenokiCo\PhpK8s\Contracts;

interface Scalable
{
/**
* Get the path, prefixed by '/', that points to the resource scale.
*
* @return string
*/
public function resourceScalePath(): string;
}
16 changes: 14 additions & 2 deletions src/Kinds/K8sDeployment.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@

use RenokiCo\PhpK8s\Contracts\InteractsWithK8sCluster;
use RenokiCo\PhpK8s\Contracts\Podable;
use RenokiCo\PhpK8s\Contracts\Scalable;
use RenokiCo\PhpK8s\Contracts\Watchable;
use RenokiCo\PhpK8s\Traits\CanScale;
use RenokiCo\PhpK8s\Traits\HasAnnotations;
use RenokiCo\PhpK8s\Traits\HasLabels;
use RenokiCo\PhpK8s\Traits\HasPods;
use RenokiCo\PhpK8s\Traits\HasSelector;
use RenokiCo\PhpK8s\Traits\HasSpec;
use RenokiCo\PhpK8s\Traits\HasTemplate;

class K8sDeployment extends K8sResource implements InteractsWithK8sCluster, Podable, Watchable
class K8sDeployment extends K8sResource implements InteractsWithK8sCluster, Podable, Scalable, Watchable
{
use HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate;
use CanScale, HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate;

/**
* The resource Kind parameter.
Expand Down Expand Up @@ -98,6 +100,16 @@ public function resourceWatchPath(): string
return "/apis/{$this->getApiVersion()}/watch/namespaces/{$this->getNamespace()}/deployments/{$this->getIdentifier()}";
}

/**
* Get the path, prefixed by '/', that points to the resource scale.
*
* @return string
*/
public function resourceScalePath(): string
{
return "/apis/{$this->getApiVersion()}/namespaces/{$this->getNamespace()}/deployments/{$this->getIdentifier()}/scale";
}

/**
* Get the selector for the pods that are owned by this resource.
*
Expand Down
28 changes: 28 additions & 0 deletions src/Kinds/K8sResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use RenokiCo\PhpK8s\Contracts\Loggable;
use RenokiCo\PhpK8s\Contracts\Scalable;
use RenokiCo\PhpK8s\Contracts\Watchable;
use RenokiCo\PhpK8s\Exceptions\KubernetesAPIException;
use RenokiCo\PhpK8s\Exceptions\KubernetesWatchException;
Expand Down Expand Up @@ -728,4 +729,31 @@ public function watchContainerLogsByName(string $name, string $container, Closur
{
return $this->whereName($name)->watchContainerLogs($container, $callback, $query);
}

/**
* Get a specific resource scaling data.
*
* @return \RenokiCo\PhpK8s\Kinds\K8sScale
*/
public function scaler(): K8sScale
{
if (! $this instanceof Scalable) {
throw new KubernetesScalingException(
'The resource '.get_class($this).' does not support scaling.'
);
}

$scaler = $this->cluster
->setResourceClass(K8sScale::class)
->runOperation(
KubernetesCluster::GET_OP,
$this->resourceScalePath(),
$this->toJsonPayload(),
['pretty' => 1]
);

$scaler->setScalableResource($this);

return $scaler;
}
}
93 changes: 93 additions & 0 deletions src/Kinds/K8sScale.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace RenokiCo\PhpK8s\Kinds;

use RenokiCo\PhpK8s\Contracts\InteractsWithK8sCluster;
use RenokiCo\PhpK8s\Traits\HasSpec;

class K8sScale extends K8sResource implements InteractsWithK8sCluster
{
use HasSpec;

/**
* The resource Kind parameter.
*
* @var null|string
*/
protected static $kind = 'Scale';

/**
* The original scalable resource for this scale.
*
* @var \RenokiCo\PhpK8s\Kinds\K8sResource
*/
protected $resource;

/**
* Wether the resource has a namespace.
*
* @var bool
*/
protected static $namespaceable = true;

/**
* The default version for the resource.
*
* @var string
*/
protected static $stableVersion = 'autoscaling/v1';

/**
* Get the path, prefixed by '/', that points to the resources list.
*
* @return string
*/
public function allResourcesPath(): string
{
return '';
}

/**
* Get the path, prefixed by '/', that points to the specific resource.
*
* @return string
*/
public function resourcePath(): string
{
return $this->resource->resourceScalePath();
}

/**
* Set the desired replicas for the scale.
*
* @param int $replicas
* @return $this
*/
public function setReplicas(int $replicas)
{
return $this->setSpec('replicas', $replicas);
}

/**
* Get the defined replicas for the scale.
*
* @return int
*/
public function getReplicas(): int
{
return $this->getSpec('replicas', 0);
}

/**
* Set the original scalable resource for this scale.
*
* @param \RenokiCo\PhpK8s\Kinds\K8sResource $resource
* @return $this
*/
public function setScalableResource(K8sResource $resource)
{
$this->resource = $resource;

return $this;
}
}
16 changes: 14 additions & 2 deletions src/Kinds/K8sStatefulSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@

use RenokiCo\PhpK8s\Contracts\InteractsWithK8sCluster;
use RenokiCo\PhpK8s\Contracts\Podable;
use RenokiCo\PhpK8s\Contracts\Scalable;
use RenokiCo\PhpK8s\Contracts\Watchable;
use RenokiCo\PhpK8s\Traits\CanScale;
use RenokiCo\PhpK8s\Traits\HasAnnotations;
use RenokiCo\PhpK8s\Traits\HasLabels;
use RenokiCo\PhpK8s\Traits\HasPods;
use RenokiCo\PhpK8s\Traits\HasSelector;
use RenokiCo\PhpK8s\Traits\HasSpec;
use RenokiCo\PhpK8s\Traits\HasTemplate;

class K8sStatefulSet extends K8sResource implements InteractsWithK8sCluster, Podable, Watchable
class K8sStatefulSet extends K8sResource implements InteractsWithK8sCluster, Podable, Scalable, Watchable
{
use HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate;
use CanScale, HasAnnotations, HasLabels, HasPods, HasSelector, HasSpec, HasTemplate;

/**
* The resource Kind parameter.
Expand Down Expand Up @@ -159,6 +161,16 @@ public function resourceWatchPath(): string
return "/apis/{$this->getApiVersion()}/watch/namespaces/{$this->getNamespace()}/statefulsets/{$this->getIdentifier()}";
}

/**
* Get the path, prefixed by '/', that points to the resource scale.
*
* @return string
*/
public function resourceScalePath(): string
{
return "/apis/{$this->getApiVersion()}/namespaces/{$this->getNamespace()}/statefulsets/{$this->getIdentifier()}/scale";
}

/**
* Get the selector for the pods that are owned by this resource.
*
Expand Down
21 changes: 21 additions & 0 deletions src/Traits/CanScale.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace RenokiCo\PhpK8s\Traits;

trait CanScale
{
/**
* Scale the current resource to a specific number of replicas.
*
* @param int $replicas
* @return \RenokiCo\PhpK8s\Kinds\K8sScale
*/
public function scale(int $replicas)
{
$scaler = $this->scaler();

$scaler->setReplicas($replicas)->update();

return $scaler;
}
}
2 changes: 1 addition & 1 deletion src/Traits/HasPods.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function getPods(array $query = ['pretty' => 1])
->all(array_merge(['labelSelector' => $labelSelector], $query));
}

/**
/**
* Check if all scheduled pods are running.
*
* @return bool
Expand Down
19 changes: 19 additions & 0 deletions tests/DeploymentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public function test_deployment_api_interaction()
$this->runUpdateTests();
$this->runWatchAllTests();
$this->runWatchTests();
$this->runScalingTests();
$this->runDeletionTests();
}

Expand Down Expand Up @@ -236,4 +237,22 @@ public function runWatchTests()

$this->assertTrue($watch);
}

public function runScalingTests()
{
$dep = $this->cluster->getDeploymentByName('mysql');

$scaler = $dep->scale(2);

while ($dep->getReadyReplicasCount() < 2 || $scaler->getReplicas() < 2) {
dump("Awaiting for deployment {$dep->getName()} to scale to 2 replicas...");
$scaler->refresh();
$dep->refresh();
sleep(1);
}

$this->assertEquals(2, $dep->getReadyReplicasCount());
$this->assertEquals(2, $scaler->getReplicas());
$this->assertCount(2, $dep->getPods());
}
}
19 changes: 19 additions & 0 deletions tests/StatefulSetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public function test_stateful_set_api_interaction()
$this->runUpdateTests();
$this->runWatchAllTests();
$this->runWatchTests();
$this->runScalingTests();
$this->runDeletionTests();
}

Expand Down Expand Up @@ -294,4 +295,22 @@ public function runWatchTests()

$this->assertTrue($watch);
}

public function runScalingTests()
{
$sts = $this->cluster->getStatefulSetByName('mysql');

$scaler = $sts->scale(2);

while ($sts->getReadyReplicasCount() < 2 || $scaler->getReplicas() < 2) {
dump("Awaiting for statefulset {$sts->getName()} to scale to 2 replicas...");
$scaler->refresh();
$sts->refresh();
sleep(1);
}

$this->assertEquals(2, $sts->getReadyReplicasCount());
$this->assertEquals(2, $scaler->getReplicas());
$this->assertCount(2, $sts->getPods());
}
}

0 comments on commit 73caae1

Please sign in to comment.