wip: swarm

This commit is contained in:
Andras Bacsai 2023-11-28 18:31:04 +01:00
parent c505a6ce9c
commit b4874c7df3
8 changed files with 172 additions and 130 deletions

View File

@ -11,6 +11,9 @@ class StopApplication
public function handle(Application $application) public function handle(Application $application)
{ {
$server = $application->destination->server; $server = $application->destination->server;
if ($server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
} else {
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0); $containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
if ($containers->count() > 0) { if ($containers->count() > 0) {
foreach ($containers as $container) { foreach ($containers as $container) {
@ -34,6 +37,8 @@ class StopApplication
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false); instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
} }
} }
}
} }
} }

View File

@ -14,8 +14,6 @@ class Dashboard extends Component
public function mount() public function mount()
{ {
$this->servers = Server::ownedByCurrentTeam()->get(); $this->servers = Server::ownedByCurrentTeam()->get();
ray($this->servers[1]);
ray($this->servers[1]->standaloneDockers);
$this->projects = Project::ownedByCurrentTeam()->get(); $this->projects = Project::ownedByCurrentTeam()->get();
} }
// public function getIptables() // public function getIptables()

View File

@ -126,7 +126,6 @@ class Select extends Component
{ {
$this->server_id = $server->id; $this->server_id = $server->id;
$this->standaloneDockers = $server->standaloneDockers; $this->standaloneDockers = $server->standaloneDockers;
ray($server);
$this->swarmDockers = $server->swarmDockers; $this->swarmDockers = $server->swarmDockers;
$this->current_step = 'destinations'; $this->current_step = 'destinations';
} }

View File

@ -156,6 +156,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Generate custom host<->ip mapping // Generate custom host<->ip mapping
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server); $allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
if (!is_null($allContainers)) {
$allContainers = format_docker_command_output_to_json($allContainers); $allContainers = format_docker_command_output_to_json($allContainers);
$ips = collect([]); $ips = collect([]);
if (count($allContainers) > 0) { if (count($allContainers) > 0) {
@ -175,6 +176,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->addHosts = $ips->map(function ($ip, $name) { $this->addHosts = $ips->map(function ($ip, $name) {
return "--add-host $name:$ip"; return "--add-host $name:$ip";
})->implode(' '); })->implode(' ');
}
if ($this->application->dockerfile_target_build) { if ($this->application->dockerfile_target_build) {
$this->buildTarget = " --target {$this->application->dockerfile_target_build} "; $this->buildTarget = " --target {$this->application->dockerfile_target_build} ";
@ -214,6 +216,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage') { if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage') {
$this->push_to_docker_registry(); $this->push_to_docker_registry();
if ($this->server->isSwarm()) {
$this->application_deployment_queue->addLogEntry("Creating / updating stack.");
$this->execute_remote_command(
[
"docker stack deploy --with-registry-auth --prune --compose-file {$this->configuration_dir}/docker-compose.yml {$this->application->uuid}"
],
);
}
} }
$this->next(ApplicationDeploymentStatus::FINISHED->value); $this->next(ApplicationDeploymentStatus::FINISHED->value);
$this->application->isConfigurationChanged(true); $this->application->isConfigurationChanged(true);
@ -534,6 +544,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function rolling_update() private function rolling_update()
{ {
if ($this->server->isSwarm()) {
// Skip this.
} else {
if (count($this->application->ports_mappings_array) > 0) { if (count($this->application->ports_mappings_array) > 0) {
$this->execute_remote_command( $this->execute_remote_command(
[ [
@ -556,8 +569,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->addLogEntry("Rolling update completed."); $this->application_deployment_queue->addLogEntry("Rolling update completed.");
} }
} }
}
private function health_check() private function health_check()
{ {
if ($this->server->isSwarm()) {
// Implement healthcheck for swarm
} else {
if ($this->application->isHealthcheckDisabled()) { if ($this->application->isHealthcheckDisabled()) {
$this->newVersionIsHealthy = true; $this->newVersionIsHealthy = true;
return; return;
@ -606,6 +623,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
} }
} }
}
private function deploy_pull_request() private function deploy_pull_request()
{ {
$this->newVersionIsHealthy = true; $this->newVersionIsHealthy = true;
@ -849,21 +867,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {
$labels = collect(generateLabelsApplication($this->application, $this->preview)); $labels = collect(generateLabelsApplication($this->application, $this->preview));
// $newHostLabel = $newLabels->filter(function ($label) {
// return str($label)->contains('Host');
// });
// $labels = $labels->reject(function ($label) {
// return str($label)->contains('Host');
// });
// ray($labels,$newLabels);
// $labels = $labels->map(function ($label) {
// $pattern = '/([a-zA-Z0-9]+)-(\d+)-(http|https)/';
// $replacement = "$1-pr-{$this->pull_request_id}-$2-$3";
// $newLabel = preg_replace($pattern, $replacement, $label);
// return $newLabel;
// });
// $labels = $labels->merge($newHostLabel);
} }
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray(); $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
$docker_compose = [ $docker_compose = [
@ -906,6 +909,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
] ]
] ]
]; ];
if ($this->server->isSwarm()) {
data_forget($docker_compose, 'services.' . $this->container_name . '.container_name');
data_forget($docker_compose, 'services.' . $this->container_name . '.expose');
data_forget($docker_compose, 'services.' . $this->container_name . '.restart');
data_forget($docker_compose, 'services.' . $this->container_name . '.mem_limit');
data_forget($docker_compose, 'services.' . $this->container_name . '.memswap_limit');
data_forget($docker_compose, 'services.' . $this->container_name . '.mem_swappiness');
data_forget($docker_compose, 'services.' . $this->container_name . '.mem_reservation');
data_forget($docker_compose, 'services.' . $this->container_name . '.cpus');
data_forget($docker_compose, 'services.' . $this->container_name . '.cpuset');
data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares');
} else {
}
if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) { if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) {
$docker_compose['services'][$this->container_name]['logging'] = [ $docker_compose['services'][$this->container_name]['logging'] = [
'driver' => 'fluentd', 'driver' => 'fluentd',

View File

@ -67,7 +67,7 @@ class Server extends BaseModel
{ {
$teamId = currentTeam()->id; $teamId = currentTeam()->id;
$selectArray = collect($select)->concat(['id']); $selectArray = collect($select)->concat(['id']);
return Server::whereTeamId($teamId)->with('settings')->select($selectArray->all())->orderBy('name'); return Server::whereTeamId($teamId)->with('settings','swarmDockers','standaloneDockers')->select($selectArray->all())->orderBy('name');
} }
static public function isUsable() static public function isUsable()
@ -87,6 +87,8 @@ class Server extends BaseModel
return $this->hasOne(ServerSetting::class); return $this->hasOne(ServerSetting::class);
} }
public function addInitialNetwork() { public function addInitialNetwork() {
ray($this->id);
if ($this->id === 0) { if ($this->id === 0) {
if ($this->isSwarm()) { if ($this->isSwarm()) {
SwarmDocker::create([ SwarmDocker::create([
@ -106,13 +108,13 @@ class Server extends BaseModel
} else { } else {
if ($this->isSwarm()) { if ($this->isSwarm()) {
SwarmDocker::create([ SwarmDocker::create([
'name' => 'coolify', 'name' => 'coolify-overlay',
'network' => 'coolify-overlay', 'network' => 'coolify-overlay',
'server_id' => $this->id, 'server_id' => $this->id,
]); ]);
} else { } else {
StandaloneDocker::create([ StandaloneDocker::create([
'name' => 'coolify', 'name' => 'coolify-overlay',
'network' => 'coolify', 'network' => 'coolify',
'server_id' => $this->id, 'server_id' => $this->id,
]); ]);
@ -452,7 +454,7 @@ class Server extends BaseModel
public function validateCoolifyNetwork($isSwarm = false) public function validateCoolifyNetwork($isSwarm = false)
{ {
if ($isSwarm) { if ($isSwarm) {
return instant_remote_process(["docker network create --driver overlay coolify-overlay >/dev/null 2>&1 || true"], $this, false); return instant_remote_process(["docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true"], $this, false);
} else { } else {
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false); return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
} }

View File

@ -123,6 +123,9 @@ function instant_remote_process(Collection|array $command, Server $server, $thro
} }
return excludeCertainErrors($process->errorOutput(), $exitCode); return excludeCertainErrors($process->errorOutput(), $exitCode);
} }
if ($output === 'null') {
$output = null;
}
return $output; return $output;
} }
function excludeCertainErrors(string $errorOutput, ?int $exitCode = null) function excludeCertainErrors(string $errorOutput, ?int $exitCode = null)

View File

@ -13,9 +13,9 @@ class SwarmDockerSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
SwarmDocker::create([ // SwarmDocker::create([
'name' => 'Swarm Docker 1', // 'name' => 'Swarm Docker 1',
'server_id' => 1, // 'server_id' => 1,
]); // ]);
} }
} }

View File

@ -69,12 +69,28 @@
@endif @endif
@if ($application->build_pack !== 'dockerimage' && $application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockerimage' && $application->build_pack !== 'dockercompose')
<h3>Docker Registry</h3> <h3>Docker Registry</h3>
@if ($application->destination->server->isSwarm())
<div>Docker Swarm requires the image to be available in a registry. More info <a class="underline"
href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div>
@else
<div>Push the built image to a docker registry. More info <a class="underline" <div>Push the built image to a docker registry. More info <a class="underline"
href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div> href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div>
@endif
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
@if ($application->build_pack === 'dockerimage') @if ($application->build_pack === 'dockerimage')
@if ($application->destination->server->isSwarm())
<x-forms.input required id="application.docker_registry_image_name" label="Docker Image" />
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
@else
<x-forms.input id="application.docker_registry_image_name" label="Docker Image" /> <x-forms.input id="application.docker_registry_image_name" label="Docker Image" />
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" /> <x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
@endif
@else
@if ($application->destination->server->isSwarm())
<x-forms.input id="application.docker_registry_image_name" required label="Docker Image" />
<x-forms.input id="application.docker_registry_image_tag"
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
label="Docker Image Tag" />
@else @else
<x-forms.input id="application.docker_registry_image_name" <x-forms.input id="application.docker_registry_image_name"
helper="Empty means it won't push the image to a docker registry." helper="Empty means it won't push the image to a docker registry."
@ -86,6 +102,8 @@
label="Docker Image Tag" /> label="Docker Image Tag" />
@endif @endif
@endif
</div> </div>
@endif @endif
@ -140,8 +158,8 @@
@endif @endif
@if ($application->build_pack === 'dockercompose') @if ($application->build_pack === 'dockercompose')
<x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button> <x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button>
<x-forms.textarea rows="10" readonly id="application.docker_compose" label="Docker Compose Content" <x-forms.textarea rows="10" readonly id="application.docker_compose"
helper="You need to modify the docker compose file." /> label="Docker Compose Content" helper="You need to modify the docker compose file." />
{{-- <x-forms.textarea rows="10" readonly id="application.docker_compose_pr" {{-- <x-forms.textarea rows="10" readonly id="application.docker_compose_pr"
label="Docker PR Compose Content" helper="You need to modify the docker compose file." /> --}} label="Docker PR Compose Content" helper="You need to modify the docker compose file." /> --}}
@endif @endif