mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-09 05:25:14 +09:00
feat(platform): add migration to convert virtual-machine to vm-disk and vm-instance (#2040)
## Summary Add migration script (#28) that converts virtual-machine HelmReleases into separate vm-disk + vm-instance HelmReleases, preserving disk data, kube-ovn IPs, and LoadBalancer IPs. ## Changes - Add migration 28 that discovers all VirtualMachine HelmReleases and for each: - Extracts values, saves kube-ovn IP and LoadBalancer IP - Switches PV reclaim to Retain, stops and deletes the VM - Creates new PVC bound to existing PV (with volumeMode detection) - Creates vm-disk and vm-instance HelmReleases with proper values - Pre-creates LoadBalancer service with metallb annotation if external=true - Cleans up old resources including protected services - Temporarily disables CDI operator and apiserver webhooks during DataVolume creation to bypass PVC existence validation - Script is fully idempotent (safe to re-run if interrupted)
This commit is contained in:
34 files changed
+911
-1471
No files matched your search
@@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
@test "Create a Virtual Machine" {
|
||||
name='test'
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: apps.cozystack.io/v1alpha1
|
||||
kind: VirtualMachine
|
||||
metadata:
|
||||
name: $name
|
||||
namespace: tenant-test
|
||||
spec:
|
||||
external: false
|
||||
externalMethod: PortList
|
||||
externalPorts:
|
||||
- 22
|
||||
instanceType: "u1.medium"
|
||||
instanceProfile: ubuntu
|
||||
systemDisk:
|
||||
image: ubuntu
|
||||
storage: 5Gi
|
||||
storageClass: replicated
|
||||
gpus: []
|
||||
resources: {}
|
||||
sshKeys:
|
||||
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPht0dPk5qQ+54g1hSX7A6AUxXJW5T6n/3d7Ga2F8gTF
|
||||
test@test
|
||||
cloudInit: |
|
||||
#cloud-config
|
||||
users:
|
||||
- name: test
|
||||
shell: /bin/bash
|
||||
sudo: ['ALL=(ALL) NOPASSWD: ALL']
|
||||
groups: sudo
|
||||
ssh_authorized_keys:
|
||||
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPht0dPk5qQ+54g1hSX7A6AUxXJW5T6n/3d7Ga2F8gTF test@test
|
||||
cloudInitSeed: ""
|
||||
EOF
|
||||
sleep 5
|
||||
kubectl -n tenant-test wait hr virtual-machine-$name --timeout=10s --for=condition=ready
|
||||
kubectl -n tenant-test wait dv virtual-machine-$name --timeout=150s --for=condition=ready
|
||||
kubectl -n tenant-test wait pvc virtual-machine-$name --timeout=100s --for=jsonpath='{.status.phase}'=Bound
|
||||
kubectl -n tenant-test wait vm virtual-machine-$name --timeout=100s --for=condition=ready
|
||||
timeout 120 sh -ec "until kubectl -n tenant-test get vmi virtual-machine-$name -o jsonpath='{.status.interfaces[0].ipAddress}' | grep -q '[0-9]'; do sleep 10; done"
|
||||
kubectl -n tenant-test delete virtualmachines.apps.cozystack.io $name
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.helmignore
|
||||
/logos
|
||||
/Makefile
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v2
|
||||
name: virtual-machine
|
||||
description: Virtual Machine (simple)
|
||||
icon: /logos/vm.svg
|
||||
type: application
|
||||
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
|
||||
appVersion: 0.14.0
|
||||
@@ -1,10 +0,0 @@
|
||||
include ../../../hack/package.mk
|
||||
|
||||
generate:
|
||||
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
|
||||
yq -o json -i '.properties.gpus.items.type = "object" | .properties.gpus.default = []' values.schema.json
|
||||
# INSTANCE_TYPES=$$(yq e '.metadata.name' -o=json -r ../../system/kubevirt-instancetypes/templates/instancetypes.yaml | yq 'split(" ") | . + [""]' -o json) \
|
||||
# && yq -i -o json ".properties.instanceType.enum = $${INSTANCE_TYPES}" values.schema.json
|
||||
PREFERENCES=$$(yq e '.metadata.name' -o=json -r ../../system/kubevirt-instancetypes/templates/preferences.yaml | yq 'split(" ") | . + [""]' -o json) \
|
||||
&& yq -i -o json ".properties.instanceProfile.enum = $${PREFERENCES}" values.schema.json
|
||||
../../../hack/update-crd.sh
|
||||
@@ -1,278 +0,0 @@
|
||||
# Virtual Machine (simple)
|
||||
|
||||
A Virtual Machine (VM) simulates computer hardware, enabling various operating systems and applications to run in an isolated environment.
|
||||
|
||||
## Deployment Details
|
||||
|
||||
The virtual machine is managed and hosted through KubeVirt, allowing you to harness the benefits of virtualization within your Kubernetes ecosystem.
|
||||
|
||||
- Docs: [KubeVirt User Guide](https://kubevirt.io/user-guide/)
|
||||
- GitHub: [KubeVirt Repository](https://github.com/kubevirt/kubevirt)
|
||||
|
||||
## Accessing virtual machine
|
||||
|
||||
You can access the virtual machine using the virtctl tool:
|
||||
- [KubeVirt User Guide - Virtctl Client Tool](https://kubevirt.io/user-guide/user_workloads/virtctl_client_tool/)
|
||||
|
||||
To access the serial console:
|
||||
|
||||
```
|
||||
virtctl console <vm>
|
||||
```
|
||||
|
||||
To access the VM using VNC:
|
||||
|
||||
```
|
||||
virtctl vnc <vm>
|
||||
```
|
||||
|
||||
To SSH into the VM:
|
||||
|
||||
```
|
||||
virtctl ssh <user>@<vm>
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
### Common parameters
|
||||
|
||||
| Name | Description | Type | Value |
|
||||
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------ |
|
||||
| `external` | Enable external access from outside the cluster. | `bool` | `false` |
|
||||
| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` |
|
||||
| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` |
|
||||
| `runStrategy` | Requested running state of the VirtualMachineInstance | `string` | `Always` |
|
||||
| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` |
|
||||
| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` |
|
||||
| `systemDisk` | System disk configuration. | `object` | `{}` |
|
||||
| `systemDisk.image` | The base image for the virtual machine. | `string` | `ubuntu` |
|
||||
| `systemDisk.storage` | The size of the disk allocated for the virtual machine. | `string` | `5Gi` |
|
||||
| `systemDisk.storageClass` | StorageClass used to store the data. | `string` | `replicated` |
|
||||
| `subnets` | Additional subnets | `[]object` | `[]` |
|
||||
| `subnets[i].name` | Subnet name | `string` | `""` |
|
||||
| `gpus` | List of GPUs to attach. | `[]object` | `[]` |
|
||||
| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` |
|
||||
| `cpuModel` | Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map | `string` | `""` |
|
||||
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |
|
||||
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` |
|
||||
| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` |
|
||||
| `resources.memory` | Amount of memory allocated. | `quantity` | `""` |
|
||||
| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` |
|
||||
| `cloudInit` | Cloud-init user data. | `string` | `""` |
|
||||
| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` |
|
||||
|
||||
|
||||
## U Series
|
||||
|
||||
The U Series is quite neutral and provides resources for
|
||||
general purpose applications.
|
||||
|
||||
*U* is the abbreviation for "Universal", hinting at the universal
|
||||
attitude towards workloads.
|
||||
|
||||
VMs of instance types will share physical CPU cores on a
|
||||
time-slice basis with other VMs.
|
||||
|
||||
### U Series Characteristics
|
||||
|
||||
Specific characteristics of this series are:
|
||||
- *Burstable CPU performance* - The workload has a baseline compute
|
||||
performance but is permitted to burst beyond this baseline, if
|
||||
excess compute resources are available.
|
||||
- *vCPU-To-Memory Ratio (1:4)* - A vCPU-to-Memory ratio of 1:4, for less
|
||||
noise per node.
|
||||
|
||||
## O Series
|
||||
|
||||
The O Series is based on the U Series, with the only difference
|
||||
being that memory is overcommitted.
|
||||
|
||||
*O* is the abbreviation for "Overcommitted".
|
||||
|
||||
### UO Series Characteristics
|
||||
|
||||
Specific characteristics of this series are:
|
||||
- *Burstable CPU performance* - The workload has a baseline compute
|
||||
performance but is permitted to burst beyond this baseline, if
|
||||
excess compute resources are available.
|
||||
- *Overcommitted Memory* - Memory is over-committed in order to achieve
|
||||
a higher workload density.
|
||||
- *vCPU-To-Memory Ratio (1:4)* - A vCPU-to-Memory ratio of 1:4, for less
|
||||
noise per node.
|
||||
|
||||
## CX Series
|
||||
|
||||
The CX Series provides exclusive compute resources for compute
|
||||
intensive applications.
|
||||
|
||||
*CX* is the abbreviation of "Compute Exclusive".
|
||||
|
||||
The exclusive resources are given to the compute threads of the
|
||||
VM. In order to ensure this, some additional cores (depending
|
||||
on the number of disks and NICs) will be requested to offload
|
||||
the IO threading from cores dedicated to the workload.
|
||||
In addition, in this series, the NUMA topology of the used
|
||||
cores is provided to the VM.
|
||||
|
||||
### CX Series Characteristics
|
||||
|
||||
Specific characteristics of this series are:
|
||||
- *Hugepages* - Hugepages are used in order to improve memory
|
||||
performance.
|
||||
- *Dedicated CPU* - Physical cores are exclusively assigned to every
|
||||
vCPU in order to provide fixed and high compute guarantees to the
|
||||
workload.
|
||||
- *Isolated emulator threads* - Hypervisor emulator threads are isolated
|
||||
from the vCPUs in order to reduce emaulation related impact on the
|
||||
workload.
|
||||
- *vNUMA* - Physical NUMA topology is reflected in the guest in order to
|
||||
optimize guest sided cache utilization.
|
||||
- *vCPU-To-Memory Ratio (1:2)* - A vCPU-to-Memory ratio of 1:2.
|
||||
|
||||
## M Series
|
||||
|
||||
The M Series provides resources for memory intensive
|
||||
applications.
|
||||
|
||||
*M* is the abbreviation of "Memory".
|
||||
|
||||
### M Series Characteristics
|
||||
|
||||
Specific characteristics of this series are:
|
||||
- *Hugepages* - Hugepages are used in order to improve memory
|
||||
performance.
|
||||
- *Burstable CPU performance* - The workload has a baseline compute
|
||||
performance but is permitted to burst beyond this baseline, if
|
||||
excess compute resources are available.
|
||||
- *vCPU-To-Memory Ratio (1:8)* - A vCPU-to-Memory ratio of 1:8, for much
|
||||
less noise per node.
|
||||
|
||||
## RT Series
|
||||
|
||||
The RT Series provides resources for realtime applications, like Oslat.
|
||||
|
||||
*RT* is the abbreviation for "realtime".
|
||||
|
||||
This series of instance types requires nodes capable of running
|
||||
realtime applications.
|
||||
|
||||
### RT Series Characteristics
|
||||
|
||||
Specific characteristics of this series are:
|
||||
- *Hugepages* - Hugepages are used in order to improve memory
|
||||
performance.
|
||||
- *Dedicated CPU* - Physical cores are exclusively assigned to every
|
||||
vCPU in order to provide fixed and high compute guarantees to the
|
||||
workload.
|
||||
- *Isolated emulator threads* - Hypervisor emulator threads are isolated
|
||||
from the vCPUs in order to reduce emaulation related impact on the
|
||||
workload.
|
||||
- *vCPU-To-Memory Ratio (1:4)* - A vCPU-to-Memory ratio of 1:4 starting from
|
||||
the medium size.
|
||||
|
||||
## Development
|
||||
|
||||
To get started with customizing or creating your own instancetypes and preferences
|
||||
see [DEVELOPMENT.md](./DEVELOPMENT.md).
|
||||
|
||||
## Resources
|
||||
|
||||
The following instancetype resources are provided by Cozystack:
|
||||
|
||||
Name | vCPUs | Memory
|
||||
-----|-------|-------
|
||||
cx1.2xlarge | 8 | 16Gi
|
||||
cx1.4xlarge | 16 | 32Gi
|
||||
cx1.8xlarge | 32 | 64Gi
|
||||
cx1.large | 2 | 4Gi
|
||||
cx1.medium | 1 | 2Gi
|
||||
cx1.xlarge | 4 | 8Gi
|
||||
gn1.2xlarge | 8 | 32Gi
|
||||
gn1.4xlarge | 16 | 64Gi
|
||||
gn1.8xlarge | 32 | 128Gi
|
||||
gn1.xlarge | 4 | 16Gi
|
||||
m1.2xlarge | 8 | 64Gi
|
||||
m1.4xlarge | 16 | 128Gi
|
||||
m1.8xlarge | 32 | 256Gi
|
||||
m1.large | 2 | 16Gi
|
||||
m1.xlarge | 4 | 32Gi
|
||||
n1.2xlarge | 16 | 32Gi
|
||||
n1.4xlarge | 32 | 64Gi
|
||||
n1.8xlarge | 64 | 128Gi
|
||||
n1.large | 4 | 8Gi
|
||||
n1.medium | 4 | 4Gi
|
||||
n1.xlarge | 8 | 16Gi
|
||||
o1.2xlarge | 8 | 32Gi
|
||||
o1.4xlarge | 16 | 64Gi
|
||||
o1.8xlarge | 32 | 128Gi
|
||||
o1.large | 2 | 8Gi
|
||||
o1.medium | 1 | 4Gi
|
||||
o1.micro | 1 | 1Gi
|
||||
o1.nano | 1 | 512Mi
|
||||
o1.small | 1 | 2Gi
|
||||
o1.xlarge | 4 | 16Gi
|
||||
rt1.2xlarge | 8 | 32Gi
|
||||
rt1.4xlarge | 16 | 64Gi
|
||||
rt1.8xlarge | 32 | 128Gi
|
||||
rt1.large | 2 | 8Gi
|
||||
rt1.medium | 1 | 4Gi
|
||||
rt1.micro | 1 | 1Gi
|
||||
rt1.small | 1 | 2Gi
|
||||
rt1.xlarge | 4 | 16Gi
|
||||
u1.2xlarge | 8 | 32Gi
|
||||
u1.2xmedium | 2 | 4Gi
|
||||
u1.4xlarge | 16 | 64Gi
|
||||
u1.8xlarge | 32 | 128Gi
|
||||
u1.large | 2 | 8Gi
|
||||
u1.medium | 1 | 4Gi
|
||||
u1.micro | 1 | 1Gi
|
||||
u1.nano | 1 | 512Mi
|
||||
u1.small | 1 | 2Gi
|
||||
u1.xlarge | 4 | 16Gi
|
||||
|
||||
The following preference resources are provided by Cozystack:
|
||||
|
||||
Name | Guest OS
|
||||
-----|---------
|
||||
alpine | Alpine
|
||||
centos.7 | CentOS 7
|
||||
centos.7.desktop | CentOS 7
|
||||
centos.stream10 | CentOS Stream 10
|
||||
centos.stream10.desktop | CentOS Stream 10
|
||||
centos.stream8 | CentOS Stream 8
|
||||
centos.stream8.desktop | CentOS Stream 8
|
||||
centos.stream8.dpdk | CentOS Stream 8
|
||||
centos.stream9 | CentOS Stream 9
|
||||
centos.stream9.desktop | CentOS Stream 9
|
||||
centos.stream9.dpdk | CentOS Stream 9
|
||||
cirros | Cirros
|
||||
fedora | Fedora (amd64)
|
||||
fedora.arm64 | Fedora (arm64)
|
||||
opensuse.leap | OpenSUSE Leap
|
||||
opensuse.tumbleweed | OpenSUSE Tumbleweed
|
||||
rhel.10 | Red Hat Enterprise Linux 10 Beta (amd64)
|
||||
rhel.10.arm64 | Red Hat Enterprise Linux 10 Beta (arm64)
|
||||
rhel.7 | Red Hat Enterprise Linux 7
|
||||
rhel.7.desktop | Red Hat Enterprise Linux 7
|
||||
rhel.8 | Red Hat Enterprise Linux 8
|
||||
rhel.8.desktop | Red Hat Enterprise Linux 8
|
||||
rhel.8.dpdk | Red Hat Enterprise Linux 8
|
||||
rhel.9 | Red Hat Enterprise Linux 9 (amd64)
|
||||
rhel.9.arm64 | Red Hat Enterprise Linux 9 (arm64)
|
||||
rhel.9.desktop | Red Hat Enterprise Linux 9 Desktop (amd64)
|
||||
rhel.9.dpdk | Red Hat Enterprise Linux 9 DPDK (amd64)
|
||||
rhel.9.realtime | Red Hat Enterprise Linux 9 Realtime (amd64)
|
||||
sles | SUSE Linux Enterprise Server
|
||||
ubuntu | Ubuntu
|
||||
windows.10 | Microsoft Windows 10
|
||||
windows.10.virtio | Microsoft Windows 10 (virtio)
|
||||
windows.11 | Microsoft Windows 11
|
||||
windows.11.virtio | Microsoft Windows 11 (virtio)
|
||||
windows.2k16 | Microsoft Windows Server 2016
|
||||
windows.2k16.virtio | Microsoft Windows Server 2016 (virtio)
|
||||
windows.2k19 | Microsoft Windows Server 2019
|
||||
windows.2k19.virtio | Microsoft Windows Server 2019 (virtio)
|
||||
windows.2k22 | Microsoft Windows Server 2022
|
||||
windows.2k22.virtio | Microsoft Windows Server 2022 (virtio)
|
||||
windows.2k25 | Microsoft Windows Server 2025
|
||||
windows.2k25.virtio | Microsoft Windows Server 2025 (virtio)
|
||||
@@ -1 +0,0 @@
|
||||
../../../library/cozy-lib
|
||||
@@ -1 +0,0 @@
|
||||
#!/bin/sh
|
||||
@@ -1,21 +0,0 @@
|
||||
<svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="144" height="144" rx="24" fill="url(#paint0_linear_687_3454)"/>
|
||||
<g clip-path="url(#clip0_687_3454)">
|
||||
<path d="M89.5039 111.707H54.497C54.1727 111.707 54.0108 111.221 54.3349 111.059L57.2522 108.952C60.3314 106.683 61.9522 102.631 60.9797 98.7412H83.021C82.0485 102.631 83.6693 106.683 86.7485 108.952L89.6658 111.059C89.99 111.221 89.8279 111.707 89.5039 111.707Z" fill="#B0B6BB"/>
|
||||
<path d="M113.328 98.741H30.6725C27.5931 98.741 25 96.148 25 93.0687V33.1032C25 30.0239 27.5931 27.4307 30.6725 27.4307H113.328C116.407 27.4307 119 30.0237 119 33.1032V93.0687C119 96.148 116.407 98.741 113.328 98.741Z" fill="#E8EDEE"/>
|
||||
<path d="M119 84.1549H25V33.1032C25 30.0239 27.5931 27.4307 30.6725 27.4307H113.328C116.407 27.4307 119 30.0237 119 33.1032L119 84.1549Z" fill="#00B3FF"/>
|
||||
<path d="M90.6374 116.569H53.3616C52.0651 116.569 50.9307 115.435 50.9307 114.138C50.9307 112.841 52.0651 111.707 53.3616 111.707H90.6374C91.9339 111.707 93.0684 112.841 93.0684 114.138C93.0684 115.435 91.9339 116.569 90.6374 116.569Z" fill="#E8EDEE"/>
|
||||
</g>
|
||||
<path d="M72.5275 53.8367C72.4431 53.8351 72.3605 53.8122 72.2873 53.7701L56.4699 44.7934C56.3983 44.7519 56.3388 44.6923 56.2973 44.6207C56.2559 44.549 56.2338 44.4678 56.2334 44.385C56.2334 44.2169 56.3258 44.0617 56.4699 43.9785L72.1912 35.0609C72.2637 35.021 72.345 35 72.4277 35C72.5105 35 72.5918 35.021 72.6643 35.0609L88.4872 44.0395C88.5591 44.0801 88.6188 44.1392 88.66 44.2107C88.7013 44.2822 88.7227 44.3635 88.7219 44.446C88.7225 44.5285 88.701 44.6097 88.6598 44.6812C88.6185 44.7526 88.5589 44.8118 88.4872 44.8525L72.7714 53.7683C72.6972 53.8114 72.6133 53.8349 72.5275 53.8367" fill="white"/>
|
||||
<path opacity="0.7" d="M70.2553 75.6517C70.171 75.6535 70.0878 75.6317 70.0151 75.5888L54.2458 66.6417C54.1715 66.6024 54.1095 66.5436 54.0661 66.4716C54.0228 66.3997 54 66.3173 54 66.2333V48.278C54 48.108 54.0924 47.9546 54.2439 47.8696C54.3172 47.8271 54.4004 47.8047 54.4851 47.8047C54.5697 47.8047 54.6529 47.8271 54.7262 47.8696L70.4937 56.8131C70.5642 56.8565 70.6225 56.917 70.6632 56.9891C70.7039 57.0612 70.7257 57.1424 70.7265 57.2251V75.1805C70.7259 75.2628 70.7042 75.3436 70.6635 75.4151C70.6227 75.4866 70.5642 75.5464 70.4937 75.5888C70.4206 75.6291 70.3387 75.6507 70.2553 75.6517" fill="white"/>
|
||||
<path opacity="0.4" d="M74.7198 75.6511C74.6333 75.6512 74.5482 75.6296 74.4722 75.5883C74.4016 75.5461 74.3432 75.4862 74.3027 75.4147C74.2623 75.3431 74.2411 75.2622 74.2412 75.18V57.3373C74.2412 57.171 74.3336 57.0158 74.4722 56.929L90.2397 47.9855C90.3119 47.9438 90.3938 47.9219 90.4771 47.9219C90.5605 47.9219 90.6424 47.9438 90.7146 47.9855C90.7876 48.0255 90.8485 48.0842 90.8911 48.1557C90.9337 48.2272 90.9563 48.3088 90.9566 48.392V66.2328C90.957 66.3164 90.9347 66.3985 90.8921 66.4704C90.8495 66.5424 90.7881 66.6014 90.7146 66.6411L74.9526 75.5883C74.8825 75.6307 74.8018 75.6525 74.7198 75.6511" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_687_3454" x1="161" y1="180" x2="3.59284e-07" y2="4.99998" gradientUnits="userSpaceOnUse">
|
||||
<stop/>
|
||||
<stop offset="1" stop-color="#595656"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_687_3454">
|
||||
<rect width="94" height="94" fill="white" transform="translate(25 25)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -1,126 +0,0 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "virtual-machine.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "virtual-machine.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "virtual-machine.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "virtual-machine.labels" -}}
|
||||
helm.sh/chart: {{ include "virtual-machine.chart" . }}
|
||||
{{ include "virtual-machine.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "virtual-machine.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "virtual-machine.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Generate a stable UUID for cloud-init re-initialization upon upgrade.
|
||||
*/}}
|
||||
{{- define "virtual-machine.stableUuid" -}}
|
||||
{{- $source := printf "%s-%s-%s" .Release.Namespace (include "virtual-machine.fullname" .) .Values.cloudInitSeed }}
|
||||
{{- $hash := sha256sum $source }}
|
||||
{{- $uuid := printf "%s-%s-4%s-9%s-%s" (substr 0 8 $hash) (substr 8 12 $hash) (substr 13 16 $hash) (substr 17 20 $hash) (substr 20 32 $hash) }}
|
||||
{{- if eq .Values.cloudInitSeed "" }}
|
||||
{{- /* Try to save previous uuid to not trigger full cloud-init again if user decided to remove the seed. */}}
|
||||
{{- $vmResource := lookup "kubevirt.io/v1" "VirtualMachine" .Release.Namespace (include "virtual-machine.fullname" .) -}}
|
||||
{{- if $vmResource }}
|
||||
{{- $existingUuid := $vmResource | dig "spec" "template" "spec" "domain" "firmware" "uuid" "" }}
|
||||
{{- if $existingUuid }}
|
||||
{{- $uuid = $existingUuid }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $uuid }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Domain resources (cpu, memory) as a JSON object.
|
||||
Used in vm.yaml for rendering and in the update hook for merge patches.
|
||||
*/}}
|
||||
{{- define "virtual-machine.domainResources" -}}
|
||||
{{- $result := dict -}}
|
||||
{{- if or .Values.cpuModel (and .Values.resources .Values.resources.cpu .Values.resources.sockets) -}}
|
||||
{{- $cpu := dict -}}
|
||||
{{- if and .Values.resources .Values.resources.cpu .Values.resources.sockets -}}
|
||||
{{- $_ := set $cpu "cores" (.Values.resources.cpu | int64) -}}
|
||||
{{- $_ := set $cpu "sockets" (.Values.resources.sockets | int64) -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.cpuModel -}}
|
||||
{{- $_ := set $cpu "model" .Values.cpuModel -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $result "cpu" $cpu -}}
|
||||
{{- end -}}
|
||||
{{- if and .Values.resources .Values.resources.memory -}}
|
||||
{{- $_ := set $result "resources" (dict "requests" (dict "memory" .Values.resources.memory)) -}}
|
||||
{{- end -}}
|
||||
{{- $result | toJson -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Node Affinity for Windows VMs
|
||||
*/}}
|
||||
{{- define "virtual-machine.nodeAffinity" -}}
|
||||
{{- if .Values._cluster.scheduling -}}
|
||||
{{- $dedicatedNodesForWindowsVMs := get .Values._cluster.scheduling "dedicatedNodesForWindowsVMs" -}}
|
||||
{{- if eq $dedicatedNodesForWindowsVMs "true" -}}
|
||||
{{- $isWindows := hasPrefix "windows" (toString .Values.instanceProfile) -}}
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
{{- if $isWindows }}
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: scheduling.cozystack.io/vm-windows
|
||||
operator: In
|
||||
values:
|
||||
- "true"
|
||||
{{- else }}
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
preference:
|
||||
matchExpressions:
|
||||
- key: scheduling.cozystack.io/vm-windows
|
||||
operator: NotIn
|
||||
values:
|
||||
- "true"
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
@@ -1,43 +0,0 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-dashboard-resources
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
resourceNames:
|
||||
- {{ include "virtual-machine.fullname" . }}
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups:
|
||||
- cozystack.io
|
||||
resources:
|
||||
- workloadmonitors
|
||||
resourceNames:
|
||||
- {{ .Release.Name }}
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-dashboard-resources
|
||||
subjects:
|
||||
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" .Release.Namespace) }}
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: {{ .Release.Name }}-dashboard-resources
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: WorkloadMonitor
|
||||
metadata:
|
||||
name: {{ $.Release.Name }}
|
||||
spec:
|
||||
replicas: 1
|
||||
minReplicas: 1
|
||||
kind: virtual-machine
|
||||
type: virtual-machine
|
||||
selector:
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
version: {{ $.Chart.Version }}
|
||||
@@ -1,40 +0,0 @@
|
||||
# if subnets are specified and image is either ubunu or alpine
|
||||
{{- if and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine")) }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "virtual-machine.fullname" $ }}-network-config
|
||||
stringData:
|
||||
networkData: |
|
||||
network:
|
||||
version: 1
|
||||
config:
|
||||
{{- if eq .Values.systemDisk.image "ubuntu" }}
|
||||
# main iface
|
||||
- type: physical
|
||||
name: enp1s0
|
||||
subnets:
|
||||
- type: dhcp
|
||||
# additional ifaces attached to subnets
|
||||
{{- range $i, $subnet := .Values.subnets }}
|
||||
- type: physical
|
||||
name: enp{{ add 2 $i }}s0
|
||||
subnets:
|
||||
- type: dhcp
|
||||
{{- end }}
|
||||
{{- else if eq .Values.systemDisk.image "alpine" }}
|
||||
#main iface
|
||||
- type: physical
|
||||
name: eth0
|
||||
subnets:
|
||||
- type: dhcp
|
||||
# additional ifaces attached to subnets
|
||||
{{- range $i, $subnet := .Values.subnets }}
|
||||
- type: physical
|
||||
name: eth{{ add1 $i }}
|
||||
subnets:
|
||||
- type: dhcp
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,33 +0,0 @@
|
||||
{{- if .Values.sshKeys }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "virtual-machine.fullname" $ }}-ssh-keys
|
||||
stringData:
|
||||
{{- range $k, $v := .Values.sshKeys }}
|
||||
key{{ $k }}: {{ quote $v }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if or .Values.cloudInit .Values.sshKeys }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "virtual-machine.fullname" . }}-cloud-init
|
||||
stringData:
|
||||
userdata: |
|
||||
{{- if .Values.cloudInit }}
|
||||
{{- .Values.cloudInit | nindent 4 }}
|
||||
{{- else if and (.Values.sshKeys) (not .Values.cloudInit) }}
|
||||
{{- /*
|
||||
We usually provide ssh keys in cloud-init metadata, because userdata it not typed and can be used for any purpose.
|
||||
However, if user provides ssh keys but not cloud-init, we still need to provide a minimal cloud-init config to avoid errors.
|
||||
*/}}
|
||||
#cloud-config
|
||||
ssh_authorized_keys:
|
||||
{{- range .Values.sshKeys }}
|
||||
- {{ quote . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "virtual-machine.fullname" . }}
|
||||
labels:
|
||||
apps.cozystack.io/user-service: "true"
|
||||
{{- include "virtual-machine.labels" . | nindent 4 }}
|
||||
{{- if .Values.external }}
|
||||
annotations:
|
||||
networking.cozystack.io/wholeIP: "true"
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ ternary "LoadBalancer" "ClusterIP" .Values.external }}
|
||||
{{- if .Values.external }}
|
||||
externalTrafficPolicy: Local
|
||||
{{- if ((include "cozy-lib.network.disableLoadBalancerNodePorts" $) | fromYaml) }}
|
||||
allocateLoadBalancerNodePorts: false
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
clusterIP: None
|
||||
{{- end }}
|
||||
selector:
|
||||
{{- include "virtual-machine.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
{{- if .Values.external }}
|
||||
{{- if and (eq .Values.externalMethod "WholeIP") (not .Values.externalPorts) }}
|
||||
- port: 65535
|
||||
{{- else }}
|
||||
{{- range .Values.externalPorts }}
|
||||
- name: port-{{ . }}
|
||||
port: {{ . }}
|
||||
targetPort: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
- port: 65535
|
||||
{{- end }}
|
||||
@@ -1,170 +0,0 @@
|
||||
{{- $vmName := include "virtual-machine.fullname" . -}}
|
||||
{{- $namespace := .Release.Namespace -}}
|
||||
|
||||
{{- $existingVM := lookup "kubevirt.io/v1" "VirtualMachine" $namespace $vmName -}}
|
||||
{{- $existingPVC := lookup "v1" "PersistentVolumeClaim" $namespace $vmName -}}
|
||||
{{- $existingService := lookup "v1" "Service" $namespace $vmName -}}
|
||||
|
||||
{{- $instanceType := .Values.instanceType | default "" -}}
|
||||
{{- $instanceProfile := .Values.instanceProfile | default "" -}}
|
||||
{{- $desiredStorage := .Values.systemDisk.storage | default "" -}}
|
||||
{{- $desiredServiceType := ternary "LoadBalancer" "ClusterIP" .Values.external -}}
|
||||
|
||||
{{- $needUpdateType := false -}}
|
||||
{{- $needUpdateProfile := false -}}
|
||||
{{- $needResizePVC := false -}}
|
||||
{{- $needRecreateService := false -}}
|
||||
{{- $needRemoveInstanceType := false -}}
|
||||
{{- $needRemoveCustomResources := false -}}
|
||||
|
||||
{{- $existingHasInstanceType := and $existingVM $existingVM.spec.instancetype -}}
|
||||
{{- if and $existingHasInstanceType (not $instanceType) -}}
|
||||
{{- $needRemoveInstanceType = true -}}
|
||||
{{- else if and $existingHasInstanceType $instanceType -}}
|
||||
{{- if not (eq $existingVM.spec.instancetype.name $instanceType) -}}
|
||||
{{- $needUpdateType = true -}}
|
||||
{{- end -}}
|
||||
{{- else if and $existingVM (not $existingHasInstanceType) $instanceType -}}
|
||||
{{- $needRemoveCustomResources = true -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if and $existingVM $existingVM.spec.preference $instanceProfile -}}
|
||||
{{- if not (eq $existingVM.spec.preference.name $instanceProfile) -}}
|
||||
{{- $needUpdateProfile = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if and $existingPVC $desiredStorage -}}
|
||||
{{- $currentStorage := $existingPVC.spec.resources.requests.storage | toString -}}
|
||||
{{- if not (eq $currentStorage $desiredStorage) -}}
|
||||
{{- $oldSize := (include "cozy-lib.resources.toFloat" $currentStorage) | float64 -}}
|
||||
{{- $newSize := (include "cozy-lib.resources.toFloat" $desiredStorage) | float64 -}}
|
||||
{{- if gt $newSize $oldSize -}}
|
||||
{{- $needResizePVC = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $existingService -}}
|
||||
{{- $currentServiceType := $existingService.spec.type -}}
|
||||
{{- if ne $currentServiceType $desiredServiceType -}}
|
||||
{{- $needRecreateService = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if or $needUpdateType $needUpdateProfile $needResizePVC $needRecreateService $needRemoveInstanceType $needRemoveCustomResources }}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: "{{ $.Release.Name }}-update-hook"
|
||||
annotations:
|
||||
helm.sh/hook: pre-install,pre-upgrade
|
||||
helm.sh/hook-weight: "0"
|
||||
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: "{{ $.Release.Name }}-update-hook"
|
||||
policy.cozystack.io/allow-to-apiserver: "true"
|
||||
spec:
|
||||
serviceAccountName: {{ $.Release.Name }}-update-hook
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: update-resources
|
||||
image: docker.io/alpine/k8s:1.33.4
|
||||
resources:
|
||||
requests:
|
||||
memory: "16Mi"
|
||||
cpu: "10m"
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
command: ["sh", "-exc"]
|
||||
args:
|
||||
- |
|
||||
{{- if $needUpdateType }}
|
||||
echo "Patching VirtualMachine for instancetype update..."
|
||||
kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \
|
||||
--type merge \
|
||||
-p '{"spec":{"instancetype":{"name": "{{ $instanceType }}", "revisionName": null}}}'
|
||||
{{- end }}
|
||||
|
||||
{{- if $needUpdateProfile }}
|
||||
echo "Patching VirtualMachine for preference update..."
|
||||
kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \
|
||||
--type merge \
|
||||
-p '{"spec":{"preference":{"name": "{{ $instanceProfile }}", "revisionName": null}}}'
|
||||
{{- end }}
|
||||
|
||||
{{- if $needRemoveInstanceType }}
|
||||
echo "Removing instancetype from VM (switching to custom resources)..."
|
||||
kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \
|
||||
--type merge \
|
||||
-p '{"spec":{"instancetype":null{{- if not $instanceProfile }},"preference":null{{- end }},"template":{"spec":{"domain":{{ include "virtual-machine.domainResources" . }}}}}}'
|
||||
{{- end }}
|
||||
|
||||
{{- if $needRemoveCustomResources }}
|
||||
echo "Removing custom CPU/memory from domain (switching to instancetype)..."
|
||||
kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \
|
||||
--type merge \
|
||||
-p '{"spec":{"instancetype":{"name":"{{ $instanceType }}","revisionName":null},"template":{"spec":{"domain":{"cpu":null,"resources":null}}}}}'
|
||||
{{- end }}
|
||||
|
||||
{{- if $needResizePVC }}
|
||||
echo "Patching PVC for storage resize..."
|
||||
kubectl patch pvc {{ $vmName }} -n {{ $namespace }} \
|
||||
--type merge \
|
||||
-p '{"spec":{"resources":{"requests":{"storage":"{{ $desiredStorage }}"}}}}'
|
||||
{{- end }}
|
||||
|
||||
{{- if $needRecreateService }}
|
||||
echo "Removing Service..."
|
||||
kubectl delete service --cascade=orphan -n {{ $namespace }} {{ $vmName }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ $.Release.Name }}-update-hook
|
||||
annotations:
|
||||
helm.sh/hook: pre-install,pre-upgrade
|
||||
helm.sh/hook-weight: "-5"
|
||||
helm.sh/hook-delete-policy: before-hook-creation
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ $.Release.Name }}-update-hook
|
||||
annotations:
|
||||
helm.sh/hook: pre-install,pre-upgrade
|
||||
helm.sh/hook-weight: "-5"
|
||||
helm.sh/hook-delete-policy: before-hook-creation
|
||||
rules:
|
||||
- apiGroups: ["kubevirt.io"]
|
||||
resources: ["virtualmachines"]
|
||||
verbs: ["patch", "get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims"]
|
||||
verbs: ["patch", "get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["services"]
|
||||
resourceNames: ["{{ $vmName }}"]
|
||||
verbs: ["delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ $.Release.Name }}-update-hook
|
||||
annotations:
|
||||
helm.sh/hook: pre-install,pre-upgrade
|
||||
helm.sh/hook-weight: "-5"
|
||||
helm.sh/hook-delete-policy: before-hook-creation
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ $.Release.Name }}-update-hook
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: {{ $.Release.Name }}-update-hook
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
{{- end }}
|
||||
@@ -1,159 +0,0 @@
|
||||
{{- if and .Values.instanceType (not (lookup "instancetype.kubevirt.io/v1beta1" "VirtualMachineClusterInstancetype" "" .Values.instanceType)) }}
|
||||
{{- fail (printf "Specified instancetype not exists in cluster: %s" .Values.instanceType) }}
|
||||
{{- end }}
|
||||
{{- if and .Values.instanceProfile (not (lookup "instancetype.kubevirt.io/v1beta1" "VirtualMachineClusterPreference" "" .Values.instanceProfile)) }}
|
||||
{{- fail (printf "Specified profile not exists in cluster: %s" .Values.instanceProfile) }}
|
||||
{{- end }}
|
||||
{{- if and (not .Values.instanceType) (not (and .Values.resources .Values.resources.cpu .Values.resources.sockets .Values.resources.memory)) }}
|
||||
{{- fail "Either instanceType or resources (cpu, sockets, memory) must be specified" }}
|
||||
{{- end }}
|
||||
|
||||
apiVersion: kubevirt.io/v1
|
||||
kind: VirtualMachine
|
||||
metadata:
|
||||
name: {{ include "virtual-machine.fullname" . }}
|
||||
labels:
|
||||
{{- include "virtual-machine.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if hasKey .Values "runStrategy" }}
|
||||
runStrategy: {{ .Values.runStrategy }}
|
||||
{{- else }}
|
||||
runStrategy: {{ ternary "Always" "Halted" (.Values.running | default true) }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.instanceType }}
|
||||
instancetype:
|
||||
kind: VirtualMachineClusterInstancetype
|
||||
name: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.instanceProfile }}
|
||||
preference:
|
||||
kind: VirtualMachineClusterPreference
|
||||
name: {{ . }}
|
||||
{{- end }}
|
||||
|
||||
dataVolumeTemplates:
|
||||
- metadata:
|
||||
name: {{ include "virtual-machine.fullname" . }}
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
spec:
|
||||
storage:
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.systemDisk.storage | quote }}
|
||||
{{- with .Values.systemDisk.storageClass }}
|
||||
storageClassName: {{ . }}
|
||||
{{- end }}
|
||||
source:
|
||||
{{- $dv := lookup "cdi.kubevirt.io/v1beta1" "DataVolume" "cozy-public" (printf "vm-image-%s" .Values.systemDisk.image) }}
|
||||
{{- if $dv }}
|
||||
pvc:
|
||||
name: vm-image-{{ .Values.systemDisk.image }}
|
||||
namespace: cozy-public
|
||||
{{- else }}
|
||||
http:
|
||||
{{- if eq .Values.systemDisk.image "cirros" }}
|
||||
url: https://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img
|
||||
{{- else if eq .Values.systemDisk.image "ubuntu" }}
|
||||
url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
|
||||
{{- else if eq .Values.systemDisk.image "fedora" }}
|
||||
url: https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2
|
||||
{{- else if eq .Values.systemDisk.image "alpine" }}
|
||||
url: https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/cloud/nocloud_alpine-3.20.8-x86_64-bios-cloudinit-r0.qcow2
|
||||
{{- else if eq .Values.systemDisk.image "talos" }}
|
||||
url: https://github.com/siderolabs/talos/releases/download/v1.7.6/nocloud-amd64.raw.xz
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
|
||||
labels:
|
||||
{{- include "virtual-machine.labels" . | nindent 8 }}
|
||||
spec:
|
||||
domain:
|
||||
{{- $domainRes := include "virtual-machine.domainResources" . | fromJson -}}
|
||||
{{- with $domainRes.cpu }}
|
||||
cpu: {{- . | toYaml | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- with $domainRes.resources }}
|
||||
resources: {{- . | toYaml | nindent 10 }}
|
||||
{{- end }}
|
||||
firmware:
|
||||
uuid: {{ include "virtual-machine.stableUuid" . }}
|
||||
devices:
|
||||
{{- if .Values.gpus }}
|
||||
gpus:
|
||||
{{- range $i, $gpu := .Values.gpus }}
|
||||
- name: gpu{{ add $i 1 }}
|
||||
deviceName: {{ $gpu.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
disks:
|
||||
- disk:
|
||||
bus: scsi
|
||||
name: systemdisk
|
||||
{{- if or .Values.cloudInit .Values.sshKeys (and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine"))) }}
|
||||
- disk:
|
||||
bus: virtio
|
||||
name: cloudinitdisk
|
||||
{{- end }}
|
||||
|
||||
interfaces:
|
||||
- name: default
|
||||
bridge: {}
|
||||
{{- if .Values.subnets }}
|
||||
{{- range $i, $subnet := .Values.subnets }}
|
||||
- name: {{ $subnet.name }}
|
||||
bridge: {}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
machine:
|
||||
type: ""
|
||||
|
||||
{{- if .Values.sshKeys }}
|
||||
accessCredentials:
|
||||
- sshPublicKey:
|
||||
source:
|
||||
secret:
|
||||
secretName: {{ include "virtual-machine.fullname" $ }}-ssh-keys
|
||||
propagationMethod:
|
||||
# keys will be injected into metadata part of cloud-init disk
|
||||
noCloud: {}
|
||||
{{- end }}
|
||||
|
||||
terminationGracePeriodSeconds: 30
|
||||
|
||||
{{- include "virtual-machine.nodeAffinity" . | nindent 6 }}
|
||||
|
||||
volumes:
|
||||
- name: systemdisk
|
||||
dataVolume:
|
||||
name: {{ include "virtual-machine.fullname" . }}
|
||||
{{- if or .Values.cloudInit .Values.sshKeys (and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine"))) }}
|
||||
- name: cloudinitdisk
|
||||
cloudInitNoCloud:
|
||||
{{- if or .Values.cloudInit .Values.sshKeys }}
|
||||
secretRef:
|
||||
name: {{ include "virtual-machine.fullname" . }}-cloud-init
|
||||
{{- end }}
|
||||
{{- if and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine")) }}
|
||||
networkDataSecretRef:
|
||||
name: {{ include "virtual-machine.fullname" . }}-network-config
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
networks:
|
||||
- name: default
|
||||
pod: {}
|
||||
{{- if .Values.subnets }}
|
||||
{{- range $i, $subnet := .Values.subnets }}
|
||||
- name: {{ $subnet.name }}
|
||||
multus:
|
||||
networkName: {{ $.Release.Namespace }}/{{ $subnet.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,230 +0,0 @@
|
||||
{
|
||||
"title": "Chart Values",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cloudInit": {
|
||||
"description": "Cloud-init user data.",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"cloudInitSeed": {
|
||||
"description": "Seed string to generate SMBIOS UUID for the VM.",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"cpuModel": {
|
||||
"description": "Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"external": {
|
||||
"description": "Enable external access from outside the cluster.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"externalMethod": {
|
||||
"description": "Method to pass through traffic to the VM.",
|
||||
"type": "string",
|
||||
"default": "PortList",
|
||||
"enum": [
|
||||
"PortList",
|
||||
"WholeIP"
|
||||
]
|
||||
},
|
||||
"externalPorts": {
|
||||
"description": "Ports to forward from outside the cluster.",
|
||||
"type": "array",
|
||||
"default": [
|
||||
22
|
||||
],
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"gpus": {
|
||||
"description": "List of GPUs to attach.",
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "The name of the GPU resource to attach.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"instanceProfile": {
|
||||
"description": "Virtual Machine preferences profile.",
|
||||
"type": "string",
|
||||
"default": "ubuntu",
|
||||
"enum": [
|
||||
"alpine",
|
||||
"centos.7",
|
||||
"centos.7.desktop",
|
||||
"centos.stream10",
|
||||
"centos.stream10.desktop",
|
||||
"centos.stream8",
|
||||
"centos.stream8.desktop",
|
||||
"centos.stream8.dpdk",
|
||||
"centos.stream9",
|
||||
"centos.stream9.desktop",
|
||||
"centos.stream9.dpdk",
|
||||
"cirros",
|
||||
"fedora",
|
||||
"fedora.arm64",
|
||||
"opensuse.leap",
|
||||
"opensuse.tumbleweed",
|
||||
"rhel.10",
|
||||
"rhel.10.arm64",
|
||||
"rhel.7",
|
||||
"rhel.7.desktop",
|
||||
"rhel.8",
|
||||
"rhel.8.desktop",
|
||||
"rhel.8.dpdk",
|
||||
"rhel.9",
|
||||
"rhel.9.arm64",
|
||||
"rhel.9.desktop",
|
||||
"rhel.9.dpdk",
|
||||
"rhel.9.realtime",
|
||||
"sles",
|
||||
"ubuntu",
|
||||
"windows.10",
|
||||
"windows.10.virtio",
|
||||
"windows.11",
|
||||
"windows.11.virtio",
|
||||
"windows.2k16",
|
||||
"windows.2k16.virtio",
|
||||
"windows.2k19",
|
||||
"windows.2k19.virtio",
|
||||
"windows.2k22",
|
||||
"windows.2k22.virtio",
|
||||
"windows.2k25",
|
||||
"windows.2k25.virtio",
|
||||
""
|
||||
]
|
||||
},
|
||||
"instanceType": {
|
||||
"description": "Virtual Machine instance type.",
|
||||
"type": "string",
|
||||
"default": "u1.medium"
|
||||
},
|
||||
"resources": {
|
||||
"description": "Resource configuration for the virtual machine.",
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"properties": {
|
||||
"cpu": {
|
||||
"description": "Number of CPU cores allocated.",
|
||||
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"x-kubernetes-int-or-string": true
|
||||
},
|
||||
"memory": {
|
||||
"description": "Amount of memory allocated.",
|
||||
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"x-kubernetes-int-or-string": true
|
||||
},
|
||||
"sockets": {
|
||||
"description": "Number of CPU sockets (vCPU topology).",
|
||||
"pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"x-kubernetes-int-or-string": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"runStrategy": {
|
||||
"description": "Requested running state of the VirtualMachineInstance",
|
||||
"type": "string",
|
||||
"default": "Always",
|
||||
"enum": [
|
||||
"Always",
|
||||
"Halted",
|
||||
"Manual",
|
||||
"RerunOnFailure",
|
||||
"Once"
|
||||
]
|
||||
},
|
||||
"sshKeys": {
|
||||
"description": "List of SSH public keys for authentication.",
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"subnets": {
|
||||
"description": "Additional subnets",
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Subnet name",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"systemDisk": {
|
||||
"description": "System disk configuration.",
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"required": [
|
||||
"image",
|
||||
"storage"
|
||||
],
|
||||
"properties": {
|
||||
"image": {
|
||||
"description": "The base image for the virtual machine.",
|
||||
"type": "string",
|
||||
"default": "ubuntu",
|
||||
"enum": [
|
||||
"ubuntu",
|
||||
"cirros",
|
||||
"alpine",
|
||||
"fedora",
|
||||
"talos"
|
||||
]
|
||||
},
|
||||
"storage": {
|
||||
"description": "The size of the disk allocated for the virtual machine.",
|
||||
"type": "string",
|
||||
"default": "5Gi"
|
||||
},
|
||||
"storageClass": {
|
||||
"description": "StorageClass used to store the data.",
|
||||
"type": "string",
|
||||
"default": "replicated"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
##
|
||||
## @section Common parameters
|
||||
##
|
||||
|
||||
## @enum {string} ExternalMethod - Method to pass through traffic to the VM.
|
||||
## @value PortList - Forward selected ports only.
|
||||
## @value WholeIP - Forward all traffic for the IP.
|
||||
|
||||
## @enum {string} SystemImage - The base image for the virtual machine.
|
||||
## @value ubuntu
|
||||
## @value cirros
|
||||
## @value alpine
|
||||
## @value fedora
|
||||
## @value talos
|
||||
|
||||
## @typedef {struct} SystemDisk - System disk configuration.
|
||||
## @field {SystemImage} image - The base image for the virtual machine.
|
||||
## @field {string} storage - The size of the disk allocated for the virtual machine.
|
||||
## @field {string} [storageClass] - StorageClass used to store the data.
|
||||
|
||||
## @typedef {struct} Subnet - Additional subnets
|
||||
## @field {string} [name] - Subnet name
|
||||
|
||||
## @typedef {struct} GPU - GPU device configuration.
|
||||
## @field {string} name - The name of the GPU resource to attach.
|
||||
|
||||
## @typedef {struct} Resources - Resource configuration for the virtual machine.
|
||||
## @field {quantity} [cpu] - Number of CPU cores allocated.
|
||||
## @field {quantity} [sockets] - Number of CPU sockets (vCPU topology).
|
||||
## @field {quantity} [memory] - Amount of memory allocated.
|
||||
|
||||
## @param {bool} external - Enable external access from outside the cluster.
|
||||
external: false
|
||||
|
||||
## @param {ExternalMethod} externalMethod - Method to pass through traffic to the VM.
|
||||
externalMethod: "PortList"
|
||||
|
||||
## @param {[]int} externalPorts - Ports to forward from outside the cluster.
|
||||
externalPorts:
|
||||
- 22
|
||||
|
||||
## @enum {string} RunStrategy - Requested running state of the VirtualMachineInstance
|
||||
## @value Always - VMI should always be running
|
||||
## @value Halted - VMI should never be running
|
||||
## @value Manual - VMI can be started/stopped using API endpoints
|
||||
## @value RerunOnFailure - VMI will initially be running and restarted if a failure occurs, but will not be restarted upon successful completion
|
||||
## @value Once - VMI will run once and not be restarted upon completion regardless if the completion is of phase Failure or Success
|
||||
|
||||
## @param {RunStrategy} runStrategy - Requested running state of the VirtualMachineInstance
|
||||
runStrategy: Always
|
||||
|
||||
## @param {string} instanceType - Virtual Machine instance type.
|
||||
## @param {string} instanceProfile - Virtual Machine preferences profile.
|
||||
instanceType: "u1.medium"
|
||||
instanceProfile: ubuntu
|
||||
|
||||
## @param {SystemDisk} systemDisk - System disk configuration.
|
||||
systemDisk:
|
||||
image: ubuntu
|
||||
storage: 5Gi
|
||||
storageClass: replicated
|
||||
|
||||
## @param {[]Subnet} subnets - Additional subnets
|
||||
subnets: []
|
||||
## Example:
|
||||
## subnets:
|
||||
## - name: subnet-84dbec17
|
||||
## - name: subnet-aa8896b5
|
||||
## - name: subnet-e9b97196
|
||||
|
||||
## @param {[]GPU} gpus - List of GPUs to attach.
|
||||
gpus: []
|
||||
## Example:
|
||||
## gpus:
|
||||
## - name: nvidia.com/GA102GL_A10
|
||||
|
||||
## @param {string} cpuModel - Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map
|
||||
cpuModel: ""
|
||||
|
||||
## @param {Resources} [resources] - Resource configuration for the virtual machine.
|
||||
resources: {}
|
||||
## Example:
|
||||
## resources:
|
||||
## cpu: "4"
|
||||
## sockets: "1"
|
||||
## memory: "8Gi"
|
||||
|
||||
## @param {[]string} sshKeys - List of SSH public keys for authentication.
|
||||
sshKeys: []
|
||||
## Example:
|
||||
## sshKeys:
|
||||
## - ssh-rsa ...
|
||||
## - ssh-ed25519 ...
|
||||
##
|
||||
|
||||
## @param {string} cloudInit - Cloud-init user data.
|
||||
cloudInit: ""
|
||||
## Example:
|
||||
## cloudInit: |
|
||||
## #cloud-config
|
||||
## password: ubuntu
|
||||
## chpasswd: { expire: False }
|
||||
##
|
||||
|
||||
## @param {string} cloudInitSeed - Seed string to generate SMBIOS UUID for the VM.
|
||||
cloudInitSeed: ""
|
||||
## Example:
|
||||
## cloudInitSeed: "upd1"
|
||||
@@ -36,28 +36,29 @@ virtctl ssh <user>@<vm>
|
||||
|
||||
### Common parameters
|
||||
|
||||
| Name | Description | Type | Value |
|
||||
| ------------------- | ------------------------------------------------------------------- | ---------- | ----------- |
|
||||
| `external` | Enable external access from outside the cluster. | `bool` | `false` |
|
||||
| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` |
|
||||
| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` |
|
||||
| `running` | Determines if the virtual machine should be running. | `bool` | `true` |
|
||||
| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` |
|
||||
| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` |
|
||||
| `disks` | List of disks to attach. | `[]object` | `[]` |
|
||||
| `disks[i].name` | Disk name. | `string` | `""` |
|
||||
| `disks[i].bus` | Disk bus type (e.g. "sata"). | `string` | `""` |
|
||||
| `subnets` | Additional subnets | `[]object` | `[]` |
|
||||
| `subnets[i].name` | Subnet name | `string` | `""` |
|
||||
| `gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` |
|
||||
| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` |
|
||||
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |
|
||||
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` |
|
||||
| `resources.memory` | Amount of memory allocated. | `quantity` | `""` |
|
||||
| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` |
|
||||
| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` |
|
||||
| `cloudInit` | Cloud-init user data. | `string` | `""` |
|
||||
| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` |
|
||||
| Name | Description | Type | Value |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------- | ----------- |
|
||||
| `external` | Enable external access from outside the cluster. | `bool` | `false` |
|
||||
| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` |
|
||||
| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` |
|
||||
| `runStrategy` | Requested running state of the VirtualMachineInstance | `string` | `Always` |
|
||||
| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` |
|
||||
| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` |
|
||||
| `disks` | List of disks to attach. | `[]object` | `[]` |
|
||||
| `disks[i].name` | Disk name. | `string` | `""` |
|
||||
| `disks[i].bus` | Disk bus type (e.g. "sata"). | `string` | `""` |
|
||||
| `subnets` | Additional subnets | `[]object` | `[]` |
|
||||
| `subnets[i].name` | Subnet name | `string` | `""` |
|
||||
| `gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` |
|
||||
| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` |
|
||||
| `cpuModel` | Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map | `string` | `""` |
|
||||
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |
|
||||
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` |
|
||||
| `resources.memory` | Amount of memory allocated. | `quantity` | `""` |
|
||||
| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` |
|
||||
| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` |
|
||||
| `cloudInit` | Cloud-init user data. | `string` | `""` |
|
||||
| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` |
|
||||
|
||||
|
||||
## U Series
|
||||
|
||||
@@ -70,6 +70,29 @@ Generate a stable UUID for cloud-init re-initialization upon upgrade.
|
||||
{{- $uuid }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Domain resources (cpu, memory) as a JSON object.
|
||||
Used in vm.yaml for rendering and in the update hook for merge patches.
|
||||
*/}}
|
||||
{{- define "virtual-machine.domainResources" -}}
|
||||
{{- $result := dict -}}
|
||||
{{- if or .Values.cpuModel (and .Values.resources .Values.resources.cpu .Values.resources.sockets) -}}
|
||||
{{- $cpu := dict -}}
|
||||
{{- if and .Values.resources .Values.resources.cpu .Values.resources.sockets -}}
|
||||
{{- $_ := set $cpu "cores" (.Values.resources.cpu | int64) -}}
|
||||
{{- $_ := set $cpu "sockets" (.Values.resources.sockets | int64) -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.cpuModel -}}
|
||||
{{- $_ := set $cpu "model" .Values.cpuModel -}}
|
||||
{{- end -}}
|
||||
{{- $_ := set $result "cpu" $cpu -}}
|
||||
{{- end -}}
|
||||
{{- if and .Values.resources .Values.resources.memory -}}
|
||||
{{- $_ := set $result "resources" (dict "requests" (dict "memory" .Values.resources.memory)) -}}
|
||||
{{- end -}}
|
||||
{{- $result | toJson -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Node Affinity for Windows VMs
|
||||
*/}}
|
||||
|
||||
@@ -2,26 +2,43 @@
|
||||
{{- $namespace := .Release.Namespace -}}
|
||||
|
||||
{{- $existingVM := lookup "kubevirt.io/v1" "VirtualMachine" $namespace $vmName -}}
|
||||
{{- $existingService := lookup "v1" "Service" $namespace $vmName -}}
|
||||
|
||||
{{- $instanceType := .Values.instanceType | default "" -}}
|
||||
{{- $instanceProfile := .Values.instanceProfile | default "" -}}
|
||||
{{- $desiredServiceType := ternary "LoadBalancer" "ClusterIP" .Values.external -}}
|
||||
|
||||
{{- $needUpdateType := false -}}
|
||||
{{- $needUpdateProfile := false -}}
|
||||
{{- $needRecreateService := false -}}
|
||||
{{- $needRemoveInstanceType := false -}}
|
||||
{{- $needRemoveCustomResources := false -}}
|
||||
|
||||
{{- if and $existingVM $instanceType -}}
|
||||
{{- $existingHasInstanceType := and $existingVM $existingVM.spec.instancetype -}}
|
||||
{{- if and $existingHasInstanceType (not $instanceType) -}}
|
||||
{{- $needRemoveInstanceType = true -}}
|
||||
{{- else if and $existingHasInstanceType $instanceType -}}
|
||||
{{- if not (eq $existingVM.spec.instancetype.name $instanceType) -}}
|
||||
{{- $needUpdateType = true -}}
|
||||
{{- end -}}
|
||||
{{- else if and $existingVM (not $existingHasInstanceType) $instanceType -}}
|
||||
{{- $needRemoveCustomResources = true -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if and $existingVM $instanceProfile -}}
|
||||
{{- if and $existingVM $existingVM.spec.preference $instanceProfile -}}
|
||||
{{- if not (eq $existingVM.spec.preference.name $instanceProfile) -}}
|
||||
{{- $needUpdateProfile = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if or $needUpdateType $needUpdateProfile }}
|
||||
{{- if $existingService -}}
|
||||
{{- $currentServiceType := $existingService.spec.type -}}
|
||||
{{- if ne $currentServiceType $desiredServiceType -}}
|
||||
{{- $needRecreateService = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if or $needUpdateType $needUpdateProfile $needRecreateService $needRemoveInstanceType $needRemoveCustomResources }}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
@@ -58,13 +75,32 @@ spec:
|
||||
--type merge \
|
||||
-p '{"spec":{"instancetype":{"name": "{{ $instanceType }}", "revisionName": null}}}'
|
||||
{{- end }}
|
||||
|
||||
|
||||
{{- if $needUpdateProfile }}
|
||||
echo "Patching VirtualMachine for preference update..."
|
||||
kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \
|
||||
--type merge \
|
||||
-p '{"spec":{"preference":{"name": "{{ $instanceProfile }}", "revisionName": null}}}'
|
||||
{{- end }}
|
||||
|
||||
{{- if $needRemoveInstanceType }}
|
||||
echo "Removing instancetype from VM (switching to custom resources)..."
|
||||
kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \
|
||||
--type merge \
|
||||
-p '{"spec":{"instancetype":null{{- if not $instanceProfile }},"preference":null{{- end }},"template":{"spec":{"domain":{{ include "virtual-machine.domainResources" . }}}}}}'
|
||||
{{- end }}
|
||||
|
||||
{{- if $needRemoveCustomResources }}
|
||||
echo "Removing custom CPU/memory from domain (switching to instancetype)..."
|
||||
kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \
|
||||
--type merge \
|
||||
-p '{"spec":{"instancetype":{"name":"{{ $instanceType }}","revisionName":null},"template":{"spec":{"domain":{"cpu":null,"resources":null}}}}}'
|
||||
{{- end }}
|
||||
|
||||
{{- if $needRecreateService }}
|
||||
echo "Removing Service..."
|
||||
kubectl delete service --cascade=orphan -n {{ $namespace }} {{ $vmName }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@@ -87,6 +123,10 @@ rules:
|
||||
- apiGroups: ["kubevirt.io"]
|
||||
resources: ["virtualmachines"]
|
||||
verbs: ["patch", "get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["services"]
|
||||
resourceNames: ["{{ $vmName }}"]
|
||||
verbs: ["delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
{{- if and .Values.instanceProfile (not (lookup "instancetype.kubevirt.io/v1beta1" "VirtualMachineClusterPreference" "" .Values.instanceProfile)) }}
|
||||
{{- fail (printf "Specified instanceProfile does not exist in the cluster: %s" .Values.instanceProfile) }}
|
||||
{{- end }}
|
||||
{{- if and (not .Values.instanceType) (not (and .Values.resources .Values.resources.cpu .Values.resources.sockets .Values.resources.memory)) }}
|
||||
{{- fail "Either instanceType or resources (cpu, sockets, memory) must be specified" }}
|
||||
{{- end }}
|
||||
|
||||
apiVersion: kubevirt.io/v1
|
||||
kind: VirtualMachine
|
||||
@@ -12,7 +15,11 @@ metadata:
|
||||
labels:
|
||||
{{- include "virtual-machine.labels" . | nindent 4 }}
|
||||
spec:
|
||||
running: {{ .Values.running }}
|
||||
{{- if hasKey .Values "runStrategy" }}
|
||||
runStrategy: {{ .Values.runStrategy }}
|
||||
{{- else }}
|
||||
runStrategy: {{ ternary "Always" "Halted" (.Values.running | default true) }}
|
||||
{{- end }}
|
||||
{{- with .Values.instanceType }}
|
||||
instancetype:
|
||||
kind: VirtualMachineClusterInstancetype
|
||||
@@ -31,15 +38,12 @@ spec:
|
||||
{{- include "virtual-machine.labels" . | nindent 8 }}
|
||||
spec:
|
||||
domain:
|
||||
{{- if and .Values.resources .Values.resources.cpu .Values.resources.sockets }}
|
||||
cpu:
|
||||
cores: {{ .Values.resources.cpu }}
|
||||
sockets: {{ .Values.resources.sockets }}
|
||||
{{- $domainRes := include "virtual-machine.domainResources" . | fromJson -}}
|
||||
{{- with $domainRes.cpu }}
|
||||
cpu: {{- . | toYaml | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- if and .Values.resources .Values.resources.memory }}
|
||||
resources:
|
||||
requests:
|
||||
memory: {{ .Values.resources.memory | quote }}
|
||||
{{- with $domainRes.resources }}
|
||||
resources: {{- . | toYaml | nindent 10 }}
|
||||
{{- end }}
|
||||
firmware:
|
||||
uuid: {{ include "virtual-machine.stableUuid" . }}
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"cpuModel": {
|
||||
"description": "Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"disks": {
|
||||
"description": "List of disks to attach.",
|
||||
"type": "array",
|
||||
@@ -175,10 +180,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"running": {
|
||||
"description": "Determines if the virtual machine should be running.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
"runStrategy": {
|
||||
"description": "Requested running state of the VirtualMachineInstance",
|
||||
"type": "string",
|
||||
"default": "Always",
|
||||
"enum": [
|
||||
"Always",
|
||||
"Halted",
|
||||
"Manual",
|
||||
"RerunOnFailure",
|
||||
"Once"
|
||||
]
|
||||
},
|
||||
"sshKeys": {
|
||||
"description": "List of SSH public keys for authentication.",
|
||||
|
||||
@@ -31,8 +31,15 @@ externalMethod: PortList
|
||||
externalPorts:
|
||||
- 22
|
||||
|
||||
## @param {bool} running - Determines if the virtual machine should be running.
|
||||
running: true
|
||||
## @enum {string} RunStrategy - Requested running state of the VirtualMachineInstance
|
||||
## @value Always - VMI should always be running
|
||||
## @value Halted - VMI should never be running
|
||||
## @value Manual - VMI can be started/stopped using API endpoints
|
||||
## @value RerunOnFailure - VMI will initially be running and restarted if a failure occurs, but will not be restarted upon successful completion
|
||||
## @value Once - VMI will run once and not be restarted upon completion regardless if the completion is of phase Failure or Success
|
||||
|
||||
## @param {RunStrategy} runStrategy - Requested running state of the VirtualMachineInstance
|
||||
runStrategy: Always
|
||||
|
||||
## @param {string} instanceType - Virtual Machine instance type.
|
||||
instanceType: "u1.medium"
|
||||
@@ -62,6 +69,9 @@ gpus: []
|
||||
## gpus:
|
||||
## - name: nvidia.com/GA102GL_A10
|
||||
|
||||
## @param {string} cpuModel - Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map
|
||||
cpuModel: ""
|
||||
|
||||
## @param {Resources} [resources] - Resource configuration for the virtual machine.
|
||||
resources: {}
|
||||
|
||||
|
||||
+777
@@ -0,0 +1,777 @@
|
||||
#!/bin/bash
|
||||
# Migration 29 --> 30
|
||||
# Convert virtual-machine HelmReleases to vm-disk + vm-instance.
|
||||
# Discovers all VirtualMachine HelmReleases by label, migrates each instance.
|
||||
# Idempotent: safe to re-run.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OLD_PREFIX="virtual-machine"
|
||||
NEW_DISK_PREFIX="vm-disk"
|
||||
NEW_INSTANCE_PREFIX="vm-instance"
|
||||
PROTECTION_WEBHOOK_NAME="protection-webhook"
|
||||
PROTECTION_WEBHOOK_NS="protection-webhook"
|
||||
CDI_APISERVER_NS="cozy-kubevirt-cdi"
|
||||
CDI_APISERVER_DEPLOY="cdi-apiserver"
|
||||
CDI_VALIDATING_WEBHOOKS="cdi-api-datavolume-validate cdi-api-dataimportcron-validate cdi-api-populator-validate cdi-api-validate"
|
||||
CDI_MUTATING_WEBHOOKS="cdi-api-datavolume-mutate"
|
||||
|
||||
# ============================================================
|
||||
# Helper functions
|
||||
# ============================================================
|
||||
|
||||
resource_exists() {
|
||||
local ns="$1" type="$2" name="$3"
|
||||
kubectl -n "$ns" get "$type" "$name" --no-headers 2>/dev/null | grep -q .
|
||||
}
|
||||
|
||||
delete_resource() {
|
||||
local ns="$1" type="$2" name="$3"
|
||||
if resource_exists "$ns" "$type" "$name"; then
|
||||
echo " [DELETE] ${type}/${name}"
|
||||
kubectl -n "$ns" delete "$type" "$name" --wait=false
|
||||
else
|
||||
echo " [SKIP] ${type}/${name} already gone"
|
||||
fi
|
||||
}
|
||||
|
||||
clone_resource() {
|
||||
local ns="$1" type="$2" old_name="$3" new_name="$4" old_instance="$5" new_instance="$6"
|
||||
|
||||
if resource_exists "$ns" "$type" "$new_name"; then
|
||||
echo " [SKIP] ${type}/${new_name} already exists"
|
||||
return 0
|
||||
fi
|
||||
if ! resource_exists "$ns" "$type" "$old_name"; then
|
||||
echo " [SKIP] ${type}/${old_name} not found"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo " [CREATE] ${type}/${new_name} from ${old_name}"
|
||||
kubectl -n "$ns" get "$type" "$old_name" -o json | \
|
||||
jq --arg new_name "$new_name" --arg old "$old_instance" --arg new "$new_instance" '
|
||||
.metadata.name = $new_name |
|
||||
del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp,
|
||||
.metadata.generation, .metadata.managedFields, .metadata.selfLink,
|
||||
.metadata.ownerReferences) |
|
||||
del(.status) |
|
||||
.metadata.labels = ((.metadata.labels // {}) | with_entries(
|
||||
if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) |
|
||||
.metadata.annotations = ((.metadata.annotations // {}) | with_entries(
|
||||
select(.key != "kubectl.kubernetes.io/last-applied-configuration") |
|
||||
if (.value | type) == "string" then .value |= gsub($old; $new) else . end))
|
||||
' | kubectl -n "$ns" apply -f -
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# STEP 1: Discover all VirtualMachine instances
|
||||
# ============================================================
|
||||
echo "=== Discovering VirtualMachine HelmReleases ==="
|
||||
INSTANCES=()
|
||||
while IFS=/ read -r ns name; do
|
||||
[ -z "$ns" ] && continue
|
||||
instance="${name#${OLD_PREFIX}-}"
|
||||
INSTANCES+=("${ns}/${instance}")
|
||||
echo " Found: ${ns}/${name} (instance=${instance})"
|
||||
done < <(kubectl get hr -A -l "apps.cozystack.io/application.kind=VirtualMachine" \
|
||||
-o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null)
|
||||
|
||||
if [ ${#INSTANCES[@]} -eq 0 ]; then
|
||||
echo " No VirtualMachine HelmReleases found. Nothing to migrate."
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=30 --dry-run=client -o yaml | kubectl apply -f-
|
||||
exit 0
|
||||
fi
|
||||
echo " Total: ${#INSTANCES[@]} instance(s)"
|
||||
|
||||
# ============================================================
|
||||
# STEP 2: Migrate each instance
|
||||
# ============================================================
|
||||
ALL_PV_NAMES=()
|
||||
ALL_PROTECTED_RESOURCES=()
|
||||
|
||||
for entry in "${INSTANCES[@]}"; do
|
||||
NAMESPACE="${entry%%/*}"
|
||||
INSTANCE="${entry#*/}"
|
||||
OLD_NAME="${OLD_PREFIX}-${INSTANCE}"
|
||||
NEW_DISK_NAME="${NEW_DISK_PREFIX}-${INSTANCE}"
|
||||
NEW_INSTANCE_NAME="${NEW_INSTANCE_PREFIX}-${INSTANCE}"
|
||||
|
||||
echo ""
|
||||
echo "======================================================================"
|
||||
echo "=== Migrating: ${OLD_NAME} -> ${NEW_DISK_NAME} + ${NEW_INSTANCE_NAME} in ${NAMESPACE}"
|
||||
echo "======================================================================"
|
||||
|
||||
# --- 2a: Suspend old HelmRelease ---
|
||||
echo " --- Suspend HelmRelease ---"
|
||||
if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then
|
||||
suspended=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false")
|
||||
if [ "$suspended" != "true" ]; then
|
||||
echo " [SUSPEND] hr/${OLD_NAME}"
|
||||
kubectl -n "$NAMESPACE" patch hr "$OLD_NAME" --type=merge -p '{"spec":{"suspend":true}}'
|
||||
else
|
||||
echo " [SKIP] hr/${OLD_NAME} already suspended"
|
||||
fi
|
||||
else
|
||||
echo " [SKIP] hr/${OLD_NAME} not found"
|
||||
fi
|
||||
|
||||
# --- 2b: Extract values from HelmRelease ---
|
||||
echo " --- Extract values ---"
|
||||
VALUES_SECRET=""
|
||||
VALUES_KEY="values.yaml"
|
||||
if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then
|
||||
VALUES_SECRET=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o json | \
|
||||
jq -r '.spec.valuesFrom // [] | map(select(.kind == "Secret" and (.name | test("cozystack-values") | not))) | .[0].name // ""')
|
||||
if [ -n "$VALUES_SECRET" ]; then
|
||||
VALUES_KEY=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o json | \
|
||||
jq -r '.spec.valuesFrom // [] | map(select(.kind == "Secret" and (.name | test("cozystack-values") | not))) | .[0].valuesKey // "values.yaml"')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Extract values from secret
|
||||
EXTERNAL="false"
|
||||
EXTERNAL_METHOD="PortList"
|
||||
EXTERNAL_PORTS='[22]'
|
||||
INSTANCE_TYPE="u1.medium"
|
||||
INSTANCE_PROFILE="ubuntu"
|
||||
RUN_STRATEGY="Always"
|
||||
SYSTEM_DISK_IMAGE="ubuntu"
|
||||
SYSTEM_DISK_STORAGE="5Gi"
|
||||
SYSTEM_DISK_STORAGE_CLASS="replicated"
|
||||
SSH_KEYS="[]"
|
||||
CLOUD_INIT=""
|
||||
CLOUD_INIT_SEED=""
|
||||
SUBNETS="[]"
|
||||
GPUS="[]"
|
||||
CPU_MODEL=""
|
||||
RESOURCES="{}"
|
||||
|
||||
if [ -n "$VALUES_SECRET" ] && resource_exists "$NAMESPACE" "secret" "$VALUES_SECRET"; then
|
||||
echo " Reading values from secret: ${VALUES_SECRET}"
|
||||
VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o jsonpath="{.data.${VALUES_KEY}}" 2>/dev/null | base64 -d 2>/dev/null || true)
|
||||
if [ -z "$VALUES_YAML" ]; then
|
||||
VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o jsonpath="{.stringData.${VALUES_KEY}}" 2>/dev/null || true)
|
||||
fi
|
||||
if [ -n "$VALUES_YAML" ]; then
|
||||
EXTERNAL=$(echo "$VALUES_YAML" | yq -r '.external // false')
|
||||
EXTERNAL_METHOD=$(echo "$VALUES_YAML" | yq -r '.externalMethod // "PortList"')
|
||||
EXTERNAL_PORTS=$(echo "$VALUES_YAML" | yq -c '.externalPorts // [22]')
|
||||
INSTANCE_TYPE=$(echo "$VALUES_YAML" | yq -r '.instanceType // "u1.medium"')
|
||||
INSTANCE_PROFILE=$(echo "$VALUES_YAML" | yq -r '.instanceProfile // "ubuntu"')
|
||||
RUN_STRATEGY=$(echo "$VALUES_YAML" | yq -r '.runStrategy // (if .running == false then "Halted" else "Always" end)')
|
||||
SYSTEM_DISK_IMAGE=$(echo "$VALUES_YAML" | yq -r '.systemDisk.image // "ubuntu"')
|
||||
SYSTEM_DISK_STORAGE=$(echo "$VALUES_YAML" | yq -r '.systemDisk.storage // "5Gi"')
|
||||
SYSTEM_DISK_STORAGE_CLASS=$(echo "$VALUES_YAML" | yq -r '.systemDisk.storageClass // "replicated"')
|
||||
SSH_KEYS=$(echo "$VALUES_YAML" | yq -c '.sshKeys // []')
|
||||
CLOUD_INIT=$(echo "$VALUES_YAML" | yq -r '.cloudInit // ""')
|
||||
CLOUD_INIT_SEED=$(echo "$VALUES_YAML" | yq -r '.cloudInitSeed // ""')
|
||||
SUBNETS=$(echo "$VALUES_YAML" | yq -c '.subnets // []')
|
||||
GPUS=$(echo "$VALUES_YAML" | yq -c '.gpus // []')
|
||||
CPU_MODEL=$(echo "$VALUES_YAML" | yq -r '.cpuModel // ""')
|
||||
RESOURCES=$(echo "$VALUES_YAML" | yq -c '.resources // {}')
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " external=${EXTERNAL}, instanceType=${INSTANCE_TYPE}, image=${SYSTEM_DISK_IMAGE}"
|
||||
|
||||
# --- 2c: Save kube-ovn IP ---
|
||||
echo " --- Save kube-ovn IP ---"
|
||||
OVN_IP=""
|
||||
OVN_MAC=""
|
||||
OVN_SUBNET="ovn-default"
|
||||
OVN_IP_NAME="${OLD_NAME}.${NAMESPACE}"
|
||||
if kubectl get ip.kubeovn.io "$OVN_IP_NAME" --no-headers 2>/dev/null | grep -q .; then
|
||||
OVN_IP=$(kubectl get ip.kubeovn.io "$OVN_IP_NAME" -o jsonpath='{.spec.ipAddress}')
|
||||
OVN_MAC=$(kubectl get ip.kubeovn.io "$OVN_IP_NAME" -o jsonpath='{.spec.macAddress}')
|
||||
OVN_SUBNET=$(kubectl get ip.kubeovn.io "$OVN_IP_NAME" -o jsonpath='{.spec.subnet}')
|
||||
echo " kube-ovn IP: ${OVN_IP}, MAC: ${OVN_MAC}, subnet: ${OVN_SUBNET}"
|
||||
else
|
||||
echo " [SKIP] No kube-ovn IP resource found"
|
||||
fi
|
||||
|
||||
# --- 2d: Save LoadBalancer IP (if external=true) ---
|
||||
echo " --- Save LoadBalancer IP ---"
|
||||
LB_IP=""
|
||||
if [ "$EXTERNAL" = "true" ] && resource_exists "$NAMESPACE" "svc" "$OLD_NAME"; then
|
||||
LB_IP=$(kubectl -n "$NAMESPACE" get svc "$OLD_NAME" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || true)
|
||||
if [ -n "$LB_IP" ]; then
|
||||
echo " LoadBalancer IP: ${LB_IP}"
|
||||
else
|
||||
echo " [WARN] external=true but no LoadBalancer IP assigned"
|
||||
fi
|
||||
else
|
||||
echo " [SKIP] Not external or no service"
|
||||
fi
|
||||
|
||||
# --- 2e: Switch PV reclaim policy to Retain ---
|
||||
echo " --- Switch PV reclaim to Retain ---"
|
||||
PV_NAME=""
|
||||
if resource_exists "$NAMESPACE" "pvc" "$OLD_NAME"; then
|
||||
PV_NAME=$(kubectl -n "$NAMESPACE" get pvc "$OLD_NAME" -o jsonpath='{.spec.volumeName}')
|
||||
if [ -n "$PV_NAME" ]; then
|
||||
ALL_PV_NAMES+=("$PV_NAME")
|
||||
current_policy=$(kubectl get pv "$PV_NAME" -o jsonpath='{.spec.persistentVolumeReclaimPolicy}')
|
||||
if [ "$current_policy" != "Retain" ]; then
|
||||
echo " [PATCH] PV ${PV_NAME}: ${current_policy} -> Retain"
|
||||
kubectl patch pv "$PV_NAME" -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
|
||||
else
|
||||
echo " [SKIP] PV ${PV_NAME} already Retain"
|
||||
fi
|
||||
fi
|
||||
elif resource_exists "$NAMESPACE" "pvc" "$NEW_DISK_NAME"; then
|
||||
PV_NAME=$(kubectl -n "$NAMESPACE" get pvc "$NEW_DISK_NAME" -o jsonpath='{.spec.volumeName}')
|
||||
ALL_PV_NAMES+=("$PV_NAME")
|
||||
echo " [SKIP] New PVC ${NEW_DISK_NAME} already exists, PV=${PV_NAME}"
|
||||
else
|
||||
echo " [WARN] No PVC found for ${OLD_NAME} or ${NEW_DISK_NAME}"
|
||||
fi
|
||||
|
||||
# --- 2f: Stop and delete the VirtualMachine ---
|
||||
echo " --- Stop VirtualMachine ---"
|
||||
if resource_exists "$NAMESPACE" "vm" "$OLD_NAME"; then
|
||||
echo " [PATCH] Stop VM ${OLD_NAME}"
|
||||
kubectl -n "$NAMESPACE" patch vm "$OLD_NAME" --type merge -p '{"spec":{"runStrategy":"Halted"}}' 2>/dev/null || \
|
||||
kubectl -n "$NAMESPACE" patch vm "$OLD_NAME" --type merge -p '{"spec":{"running":false}}' 2>/dev/null || true
|
||||
|
||||
echo " Waiting for VMI to terminate..."
|
||||
kubectl -n "$NAMESPACE" wait --for=delete vmi "$OLD_NAME" --timeout=120s 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# --- 2g: Delete VirtualMachine CR (cascades to DataVolume and PVC) ---
|
||||
echo " --- Delete VirtualMachine CR ---"
|
||||
if resource_exists "$NAMESPACE" "vm" "$OLD_NAME"; then
|
||||
echo " [DELETE] vm/${OLD_NAME}"
|
||||
kubectl -n "$NAMESPACE" delete vm "$OLD_NAME" --wait=true --timeout=60s 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo " Waiting for old PVC to be deleted..."
|
||||
for i in $(seq 1 30); do
|
||||
if ! resource_exists "$NAMESPACE" "pvc" "$OLD_NAME"; then
|
||||
echo " [OK] PVC ${OLD_NAME} deleted"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# If PVC still exists (orphaned), delete it manually
|
||||
if resource_exists "$NAMESPACE" "pvc" "$OLD_NAME"; then
|
||||
echo " [DELETE] Orphaned pvc/${OLD_NAME}"
|
||||
kubectl -n "$NAMESPACE" delete pvc "$OLD_NAME" --wait=false 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Delete orphaned DataVolume if still present
|
||||
if resource_exists "$NAMESPACE" "dv" "$OLD_NAME"; then
|
||||
echo " [DELETE] Orphaned dv/${OLD_NAME}"
|
||||
kubectl -n "$NAMESPACE" delete dv "$OLD_NAME" --wait=false 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# --- 2h: Create new PVC for vm-disk ---
|
||||
echo " --- Create new PVC for vm-disk ---"
|
||||
if [ -n "$PV_NAME" ]; then
|
||||
if resource_exists "$NAMESPACE" "pvc" "$NEW_DISK_NAME"; then
|
||||
new_phase=$(kubectl -n "$NAMESPACE" get pvc "$NEW_DISK_NAME" \
|
||||
-o jsonpath='{.status.phase}' 2>/dev/null || echo "unknown")
|
||||
if [ "$new_phase" = "Bound" ]; then
|
||||
echo " [SKIP] PVC ${NEW_DISK_NAME} already Bound"
|
||||
fi
|
||||
else
|
||||
PV_VOLUME_MODE=$(kubectl get pv "$PV_NAME" -o jsonpath='{.spec.volumeMode}' 2>/dev/null || echo "Filesystem")
|
||||
echo " [CREATE] PVC ${NEW_DISK_NAME} -> PV ${PV_NAME} (volumeMode=${PV_VOLUME_MODE})"
|
||||
cat <<PVCEOF | kubectl -n "$NAMESPACE" apply -f -
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: ${NEW_DISK_NAME}
|
||||
namespace: ${NAMESPACE}
|
||||
annotations:
|
||||
cdi.kubevirt.io/allowClaimAdoption: "true"
|
||||
labels:
|
||||
app.kubernetes.io/instance: ${NEW_DISK_NAME}
|
||||
spec:
|
||||
accessModes: ["ReadWriteMany"]
|
||||
volumeMode: ${PV_VOLUME_MODE}
|
||||
storageClassName: "${SYSTEM_DISK_STORAGE_CLASS}"
|
||||
volumeName: "${PV_NAME}"
|
||||
resources:
|
||||
requests:
|
||||
storage: "${SYSTEM_DISK_STORAGE}"
|
||||
PVCEOF
|
||||
|
||||
new_pvc_uid=$(kubectl -n "$NAMESPACE" get pvc "$NEW_DISK_NAME" -o jsonpath='{.metadata.uid}')
|
||||
echo " [PATCH] Rebind PV ${PV_NAME} -> ${NEW_DISK_NAME}"
|
||||
kubectl patch pv "$PV_NAME" --type=merge -p "{
|
||||
\"spec\":{\"claimRef\":{\"apiVersion\":\"v1\",\"kind\":\"PersistentVolumeClaim\",
|
||||
\"name\":\"${NEW_DISK_NAME}\",\"namespace\":\"${NAMESPACE}\",\"uid\":\"${new_pvc_uid}\"}}
|
||||
}"
|
||||
|
||||
echo " Waiting for PVC ${NEW_DISK_NAME} to bind..."
|
||||
kubectl -n "$NAMESPACE" wait pvc "$NEW_DISK_NAME" \
|
||||
--for=jsonpath='{.status.phase}'=Bound --timeout=60s
|
||||
echo " [OK] PVC ${NEW_DISK_NAME} is Bound"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 2i: Clone Secrets ---
|
||||
echo " --- Clone Secrets ---"
|
||||
kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \
|
||||
| grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values" \
|
||||
| while IFS= read -r secret; do
|
||||
old_secret_name="${secret#secret/}"
|
||||
suffix="${old_secret_name#${OLD_NAME}}"
|
||||
new_secret_name="${NEW_INSTANCE_NAME}${suffix}"
|
||||
clone_resource "$NAMESPACE" "secret" "$old_secret_name" "$new_secret_name" "$OLD_NAME" "$NEW_INSTANCE_NAME"
|
||||
done
|
||||
|
||||
# --- 2j: Clone WorkloadMonitor ---
|
||||
echo " --- Clone WorkloadMonitor ---"
|
||||
clone_resource "$NAMESPACE" "workloadmonitor.cozystack.io" "$OLD_NAME" "$NEW_INSTANCE_NAME" "$OLD_NAME" "$NEW_INSTANCE_NAME"
|
||||
|
||||
# --- 2k: Migrate kube-ovn IP ---
|
||||
echo " --- Migrate kube-ovn IP ---"
|
||||
NEW_OVN_IP_NAME="${NEW_INSTANCE_NAME}.${NAMESPACE}"
|
||||
if [ -n "$OVN_IP" ]; then
|
||||
if kubectl get ip.kubeovn.io "$NEW_OVN_IP_NAME" --no-headers 2>/dev/null | grep -q .; then
|
||||
echo " [SKIP] ip.kubeovn.io/${NEW_OVN_IP_NAME} already exists"
|
||||
else
|
||||
echo " [CREATE] ip.kubeovn.io/${NEW_OVN_IP_NAME}"
|
||||
cat <<IPEOF | kubectl apply -f -
|
||||
apiVersion: kubeovn.io/v1
|
||||
kind: IP
|
||||
metadata:
|
||||
name: ${NEW_OVN_IP_NAME}
|
||||
labels:
|
||||
${OVN_SUBNET}: ""
|
||||
ovn.kubernetes.io/ip_reserved: "true"
|
||||
ovn.kubernetes.io/subnet: ${OVN_SUBNET}
|
||||
spec:
|
||||
ipAddress: "${OVN_IP}"
|
||||
v4IpAddress: "${OVN_IP}"
|
||||
v6IpAddress: ""
|
||||
macAddress: "${OVN_MAC}"
|
||||
namespace: "${NAMESPACE}"
|
||||
podName: "${NEW_INSTANCE_NAME}"
|
||||
podType: "VirtualMachine"
|
||||
subnet: "${OVN_SUBNET}"
|
||||
attachIps: []
|
||||
attachMacs: []
|
||||
attachSubnets: []
|
||||
IPEOF
|
||||
fi
|
||||
|
||||
# Delete old kube-ovn IP resource
|
||||
if kubectl get ip.kubeovn.io "$OVN_IP_NAME" --no-headers 2>/dev/null | grep -q .; then
|
||||
echo " [DELETE] ip.kubeovn.io/${OVN_IP_NAME}"
|
||||
kubectl delete ip.kubeovn.io "$OVN_IP_NAME" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 2l: Create vm-disk values secret ---
|
||||
echo " --- Create vm-disk values secret ---"
|
||||
DISK_VALUES_SECRET="${NEW_DISK_NAME}-values"
|
||||
if ! resource_exists "$NAMESPACE" "secret" "$DISK_VALUES_SECRET"; then
|
||||
echo " [CREATE] secret/${DISK_VALUES_SECRET}"
|
||||
cat <<DSEOF | kubectl -n "$NAMESPACE" apply -f -
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ${DISK_VALUES_SECRET}
|
||||
namespace: ${NAMESPACE}
|
||||
stringData:
|
||||
values.yaml: |
|
||||
source:
|
||||
image:
|
||||
name: "${SYSTEM_DISK_IMAGE}"
|
||||
optical: false
|
||||
storage: "${SYSTEM_DISK_STORAGE}"
|
||||
storageClass: "${SYSTEM_DISK_STORAGE_CLASS}"
|
||||
DSEOF
|
||||
else
|
||||
echo " [SKIP] secret/${DISK_VALUES_SECRET} already exists"
|
||||
fi
|
||||
|
||||
# --- 2m: Create vm-disk HelmRelease (suspended) ---
|
||||
echo " --- Create vm-disk HelmRelease (suspended) ---"
|
||||
if resource_exists "$NAMESPACE" "hr" "$NEW_DISK_NAME"; then
|
||||
echo " [SKIP] hr/${NEW_DISK_NAME} already exists"
|
||||
else
|
||||
echo " [CREATE] hr/${NEW_DISK_NAME}"
|
||||
cat <<DHREOF | kubectl -n "$NAMESPACE" apply -f -
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: ${NEW_DISK_NAME}
|
||||
namespace: ${NAMESPACE}
|
||||
labels:
|
||||
sharding.fluxcd.io/key: tenants
|
||||
apps.cozystack.io/application.kind: VMDisk
|
||||
apps.cozystack.io/application.name: vm-disk
|
||||
spec:
|
||||
suspend: true
|
||||
chartRef:
|
||||
kind: ExternalArtifact
|
||||
name: cozystack-vm-disk-application-kubevirt-vm-disk
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
remediation:
|
||||
retries: -1
|
||||
upgrade:
|
||||
force: true
|
||||
remediation:
|
||||
retries: -1
|
||||
valuesFrom:
|
||||
- kind: Secret
|
||||
name: ${DISK_VALUES_SECRET}
|
||||
valuesKey: values.yaml
|
||||
DHREOF
|
||||
fi
|
||||
|
||||
# --- 2n: Create vm-instance values secret ---
|
||||
echo " --- Create vm-instance values secret ---"
|
||||
INSTANCE_VALUES_SECRET="${NEW_INSTANCE_NAME}-values"
|
||||
if ! resource_exists "$NAMESPACE" "secret" "$INSTANCE_VALUES_SECRET"; then
|
||||
echo " [CREATE] secret/${INSTANCE_VALUES_SECRET}"
|
||||
cat <<ISEOF | kubectl -n "$NAMESPACE" apply -f -
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ${INSTANCE_VALUES_SECRET}
|
||||
namespace: ${NAMESPACE}
|
||||
stringData:
|
||||
values.yaml: |
|
||||
external: ${EXTERNAL}
|
||||
externalMethod: "${EXTERNAL_METHOD}"
|
||||
externalPorts: ${EXTERNAL_PORTS}
|
||||
runStrategy: "${RUN_STRATEGY}"
|
||||
instanceType: "${INSTANCE_TYPE}"
|
||||
instanceProfile: "${INSTANCE_PROFILE}"
|
||||
disks:
|
||||
- name: "${INSTANCE}"
|
||||
subnets: ${SUBNETS}
|
||||
gpus: ${GPUS}
|
||||
cpuModel: "${CPU_MODEL}"
|
||||
resources: ${RESOURCES}
|
||||
sshKeys: ${SSH_KEYS}
|
||||
cloudInit: $(printf '%s' "$CLOUD_INIT" | jq -Rs .)
|
||||
cloudInitSeed: "${CLOUD_INIT_SEED}"
|
||||
ISEOF
|
||||
else
|
||||
echo " [SKIP] secret/${INSTANCE_VALUES_SECRET} already exists"
|
||||
fi
|
||||
|
||||
# --- 2o: Create vm-instance HelmRelease (suspended) ---
|
||||
echo " --- Create vm-instance HelmRelease (suspended) ---"
|
||||
if resource_exists "$NAMESPACE" "hr" "$NEW_INSTANCE_NAME"; then
|
||||
echo " [SKIP] hr/${NEW_INSTANCE_NAME} already exists"
|
||||
else
|
||||
# Build valuesFrom list - include cozystack-values if the old HR had it
|
||||
COZYSTACK_VALUES_FROM=""
|
||||
if resource_exists "$NAMESPACE" "secret" "cozystack-values"; then
|
||||
COZYSTACK_VALUES_FROM=' - kind: Secret
|
||||
name: cozystack-values'
|
||||
fi
|
||||
|
||||
echo " [CREATE] hr/${NEW_INSTANCE_NAME}"
|
||||
cat <<IHREOF | kubectl -n "$NAMESPACE" apply -f -
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: ${NEW_INSTANCE_NAME}
|
||||
namespace: ${NAMESPACE}
|
||||
labels:
|
||||
sharding.fluxcd.io/key: tenants
|
||||
apps.cozystack.io/application.kind: VMInstance
|
||||
apps.cozystack.io/application.name: vm-instance
|
||||
spec:
|
||||
suspend: true
|
||||
chartRef:
|
||||
kind: ExternalArtifact
|
||||
name: cozystack-vm-instance-application-kubevirt-vm-instance
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
remediation:
|
||||
retries: -1
|
||||
upgrade:
|
||||
force: true
|
||||
remediation:
|
||||
retries: -1
|
||||
valuesFrom:
|
||||
- kind: Secret
|
||||
name: ${INSTANCE_VALUES_SECRET}
|
||||
valuesKey: values.yaml
|
||||
${COZYSTACK_VALUES_FROM}
|
||||
IHREOF
|
||||
fi
|
||||
|
||||
# --- 2p: If external=true, pre-create LoadBalancer service with metallb annotation ---
|
||||
echo " --- Handle external service ---"
|
||||
if [ "$EXTERNAL" = "true" ] && [ -n "$LB_IP" ]; then
|
||||
if resource_exists "$NAMESPACE" "svc" "$NEW_INSTANCE_NAME"; then
|
||||
echo " [SKIP] svc/${NEW_INSTANCE_NAME} already exists"
|
||||
else
|
||||
echo " [CREATE] svc/${NEW_INSTANCE_NAME} with metallb IP ${LB_IP}"
|
||||
cat <<SVCEOF | kubectl -n "$NAMESPACE" apply -f -
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ${NEW_INSTANCE_NAME}
|
||||
namespace: ${NAMESPACE}
|
||||
labels:
|
||||
apps.cozystack.io/user-service: "true"
|
||||
app.kubernetes.io/name: vm-instance
|
||||
app.kubernetes.io/instance: ${NEW_INSTANCE_NAME}
|
||||
annotations:
|
||||
networking.cozystack.io/wholeIP: "true"
|
||||
metallb.universe.tf/loadBalancerIPs: "${LB_IP}"
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
selector:
|
||||
app.kubernetes.io/name: vm-instance
|
||||
app.kubernetes.io/instance: ${NEW_INSTANCE_NAME}
|
||||
ports:
|
||||
- port: 65535
|
||||
SVCEOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 2q: Delete old resources ---
|
||||
echo " --- Delete old resources ---"
|
||||
kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \
|
||||
| grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values" \
|
||||
| while IFS= read -r secret; do
|
||||
old_secret_name="${secret#secret/}"
|
||||
delete_resource "$NAMESPACE" "secret" "$old_secret_name"
|
||||
done
|
||||
|
||||
delete_resource "$NAMESPACE" "workloadmonitor.cozystack.io" "$OLD_NAME"
|
||||
|
||||
if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then
|
||||
kubectl -n "$NAMESPACE" patch hr "$OLD_NAME" --type=json \
|
||||
-p='[{"op":"remove","path":"/metadata/finalizers"}]' 2>/dev/null || true
|
||||
delete_resource "$NAMESPACE" "hr" "$OLD_NAME"
|
||||
fi
|
||||
|
||||
echo " [DELETE] secrets with label owner=helm,name=${OLD_NAME}"
|
||||
kubectl -n "$NAMESPACE" delete secret -l "owner=helm,name=${OLD_NAME}" 2>/dev/null || true
|
||||
|
||||
# Delete old values secret
|
||||
if [ -n "$VALUES_SECRET" ]; then
|
||||
delete_resource "$NAMESPACE" "secret" "$VALUES_SECRET"
|
||||
fi
|
||||
|
||||
# Collect protected resources for batch deletion
|
||||
if resource_exists "$NAMESPACE" "svc" "$OLD_NAME"; then
|
||||
ALL_PROTECTED_RESOURCES+=("${NAMESPACE}:svc/${OLD_NAME}")
|
||||
fi
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# STEP 3: Delete protected resources (Services)
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 3: Delete protected resources ---"
|
||||
|
||||
if [ ${#ALL_PROTECTED_RESOURCES[@]} -gt 0 ]; then
|
||||
WEBHOOK_EXISTS=false
|
||||
if kubectl -n "$PROTECTION_WEBHOOK_NS" get deploy "$PROTECTION_WEBHOOK_NAME" --no-headers 2>/dev/null | grep -q .; then
|
||||
WEBHOOK_EXISTS=true
|
||||
fi
|
||||
|
||||
if [ "$WEBHOOK_EXISTS" = "true" ]; then
|
||||
echo " --- Temporarily disabling protection-webhook ---"
|
||||
|
||||
WEBHOOK_REPLICAS=$(kubectl -n "$PROTECTION_WEBHOOK_NS" get deploy "$PROTECTION_WEBHOOK_NAME" \
|
||||
-o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1")
|
||||
|
||||
echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> 0 (was ${WEBHOOK_REPLICAS})"
|
||||
kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" --replicas=0
|
||||
|
||||
echo " [PATCH] Set failurePolicy=Ignore on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}"
|
||||
kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \
|
||||
jq '.webhooks[].failurePolicy = "Ignore"' | \
|
||||
kubectl apply -f - 2>/dev/null || true
|
||||
|
||||
echo " Waiting for webhook pods to terminate..."
|
||||
kubectl -n "$PROTECTION_WEBHOOK_NS" wait --for=delete pod \
|
||||
-l app.kubernetes.io/name=protection-webhook --timeout=60s 2>/dev/null || true
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
for entry in "${ALL_PROTECTED_RESOURCES[@]}"; do
|
||||
ns="${entry%%:*}"
|
||||
res="${entry#*:}"
|
||||
echo " [DELETE] ${ns}/${res}"
|
||||
kubectl -n "$ns" delete "$res" --wait=false 2>/dev/null || true
|
||||
done
|
||||
|
||||
if [ "$WEBHOOK_EXISTS" = "true" ]; then
|
||||
echo " [PATCH] Set failurePolicy=Fail on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}"
|
||||
kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \
|
||||
jq '.webhooks[].failurePolicy = "Fail"' | \
|
||||
kubectl apply -f - 2>/dev/null || true
|
||||
|
||||
echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> ${WEBHOOK_REPLICAS}"
|
||||
kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" \
|
||||
--replicas="$WEBHOOK_REPLICAS"
|
||||
echo " --- protection-webhook restored ---"
|
||||
fi
|
||||
else
|
||||
echo " [SKIP] No protected resources to delete"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# STEP 4: Restore PV reclaim policies
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 4: Restore PV reclaim policies ---"
|
||||
for pv_name in "${ALL_PV_NAMES[@]}"; do
|
||||
if [ -n "$pv_name" ]; then
|
||||
current_policy=$(kubectl get pv "$pv_name" \
|
||||
-o jsonpath='{.spec.persistentVolumeReclaimPolicy}' 2>/dev/null || echo "unknown")
|
||||
if [ "$current_policy" = "Retain" ]; then
|
||||
echo " [PATCH] PV ${pv_name}: Retain -> Delete"
|
||||
kubectl patch pv "$pv_name" -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'
|
||||
else
|
||||
echo " [SKIP] PV ${pv_name} already ${current_policy}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# STEP 5: Temporarily disable CDI datavolume webhooks
|
||||
# ============================================================
|
||||
# CDI's datavolume-validate webhook rejects DataVolume creation when a PVC
|
||||
# with the same name already exists. We must disable it so that vm-disk
|
||||
# HelmReleases can reconcile and adopt the pre-created PVCs.
|
||||
# We scale down both cdi-operator (which recreates webhook configs) and
|
||||
# cdi-apiserver (which serves the webhooks), then delete webhook configs.
|
||||
# Both are restored after vm-disk HRs reconcile.
|
||||
echo ""
|
||||
echo "--- Step 5: Temporarily disable CDI webhooks ---"
|
||||
|
||||
CDI_OPERATOR_REPLICAS=$(kubectl -n "$CDI_APISERVER_NS" get deploy cdi-operator \
|
||||
-o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1")
|
||||
CDI_APISERVER_REPLICAS=$(kubectl -n "$CDI_APISERVER_NS" get deploy "$CDI_APISERVER_DEPLOY" \
|
||||
-o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1")
|
||||
|
||||
echo " [SCALE] cdi-operator -> 0 (was ${CDI_OPERATOR_REPLICAS})"
|
||||
kubectl -n "$CDI_APISERVER_NS" scale deploy cdi-operator --replicas=0
|
||||
echo " [SCALE] ${CDI_APISERVER_DEPLOY} -> 0 (was ${CDI_APISERVER_REPLICAS})"
|
||||
kubectl -n "$CDI_APISERVER_NS" scale deploy "$CDI_APISERVER_DEPLOY" --replicas=0
|
||||
|
||||
echo " Waiting for cdi pods to terminate..."
|
||||
kubectl -n "$CDI_APISERVER_NS" wait --for=delete pod \
|
||||
-l cdi.kubevirt.io=cdi-apiserver --timeout=60s 2>/dev/null || true
|
||||
kubectl -n "$CDI_APISERVER_NS" wait --for=delete pod \
|
||||
-l name=cdi-operator --timeout=60s 2>/dev/null || true
|
||||
|
||||
for wh in $CDI_VALIDATING_WEBHOOKS; do
|
||||
if kubectl get validatingwebhookconfiguration "$wh" >/dev/null 2>&1; then
|
||||
echo " [DELETE] validatingwebhookconfiguration/${wh}"
|
||||
kubectl delete validatingwebhookconfiguration "$wh" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
for wh in $CDI_MUTATING_WEBHOOKS; do
|
||||
if kubectl get mutatingwebhookconfiguration "$wh" >/dev/null 2>&1; then
|
||||
echo " [DELETE] mutatingwebhookconfiguration/${wh}"
|
||||
kubectl delete mutatingwebhookconfiguration "$wh" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
sleep 2
|
||||
|
||||
# ============================================================
|
||||
# STEP 6: Unsuspend vm-disk HelmReleases first
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 6: Unsuspend vm-disk HelmReleases ---"
|
||||
for entry in "${INSTANCES[@]}"; do
|
||||
ns="${entry%%/*}"
|
||||
instance="${entry#*/}"
|
||||
disk_name="${NEW_DISK_PREFIX}-${instance}"
|
||||
if resource_exists "$ns" "hr" "$disk_name"; then
|
||||
suspended=$(kubectl -n "$ns" get hr "$disk_name" \
|
||||
-o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false")
|
||||
if [ "$suspended" = "true" ]; then
|
||||
echo " [UNSUSPEND] ${ns}/hr/${disk_name}"
|
||||
kubectl -n "$ns" patch hr "$disk_name" --type=merge -p '{"spec":{"suspend":false}}'
|
||||
else
|
||||
echo " [SKIP] ${ns}/hr/${disk_name} already not suspended"
|
||||
fi
|
||||
# Force immediate reconciliation
|
||||
echo " [TRIGGER] Reconcile ${ns}/hr/${disk_name}"
|
||||
kubectl -n "$ns" annotate hr "$disk_name" --overwrite \
|
||||
"reconcile.fluxcd.io/requestedAt=$(date +%s)" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Wait for disk HelmReleases to become ready
|
||||
echo " Waiting for vm-disk HelmReleases to reconcile..."
|
||||
for entry in "${INSTANCES[@]}"; do
|
||||
ns="${entry%%/*}"
|
||||
instance="${entry#*/}"
|
||||
disk_name="${NEW_DISK_PREFIX}-${instance}"
|
||||
for i in $(seq 1 60); do
|
||||
ready=$(kubectl -n "$ns" get hr "$disk_name" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true)
|
||||
if [ "$ready" = "True" ]; then
|
||||
echo " [OK] ${ns}/hr/${disk_name} is Ready"
|
||||
break
|
||||
fi
|
||||
if [ "$i" = "60" ]; then
|
||||
echo " [WARN] ${ns}/hr/${disk_name} did not become Ready within timeout"
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
done
|
||||
|
||||
# ============================================================
|
||||
# STEP 7: Restore CDI webhooks
|
||||
# ============================================================
|
||||
# Scale cdi-operator and cdi-apiserver back up.
|
||||
# cdi-apiserver will recreate webhook configurations automatically on start.
|
||||
echo ""
|
||||
echo "--- Step 7: Restore CDI webhooks ---"
|
||||
|
||||
echo " [SCALE] cdi-operator -> ${CDI_OPERATOR_REPLICAS}"
|
||||
kubectl -n "$CDI_APISERVER_NS" scale deploy cdi-operator \
|
||||
--replicas="$CDI_OPERATOR_REPLICAS"
|
||||
echo " [SCALE] ${CDI_APISERVER_DEPLOY} -> ${CDI_APISERVER_REPLICAS}"
|
||||
kubectl -n "$CDI_APISERVER_NS" scale deploy "$CDI_APISERVER_DEPLOY" \
|
||||
--replicas="$CDI_APISERVER_REPLICAS"
|
||||
|
||||
echo " Waiting for CDI to be ready..."
|
||||
kubectl -n "$CDI_APISERVER_NS" rollout status deploy cdi-operator --timeout=120s 2>/dev/null || true
|
||||
kubectl -n "$CDI_APISERVER_NS" rollout status deploy "$CDI_APISERVER_DEPLOY" --timeout=120s 2>/dev/null || true
|
||||
echo " --- CDI webhooks restored ---"
|
||||
|
||||
# ============================================================
|
||||
# STEP 8: Unsuspend vm-instance HelmReleases
|
||||
# ============================================================
|
||||
echo ""
|
||||
echo "--- Step 8: Unsuspend vm-instance HelmReleases ---"
|
||||
for entry in "${INSTANCES[@]}"; do
|
||||
ns="${entry%%/*}"
|
||||
instance="${entry#*/}"
|
||||
instance_name="${NEW_INSTANCE_PREFIX}-${instance}"
|
||||
if resource_exists "$ns" "hr" "$instance_name"; then
|
||||
suspended=$(kubectl -n "$ns" get hr "$instance_name" \
|
||||
-o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false")
|
||||
if [ "$suspended" = "true" ]; then
|
||||
echo " [UNSUSPEND] ${ns}/hr/${instance_name}"
|
||||
kubectl -n "$ns" patch hr "$instance_name" --type=merge -p '{"spec":{"suspend":false}}'
|
||||
else
|
||||
echo " [SKIP] ${ns}/hr/${instance_name} already not suspended"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Migration complete (${#INSTANCES[@]} instance(s)) ==="
|
||||
|
||||
# Stamp version
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=30 --dry-run=client -o yaml | kubectl apply -f-
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: PackageSource
|
||||
metadata:
|
||||
name: cozystack.virtual-machine-application
|
||||
spec:
|
||||
sourceRef:
|
||||
kind: OCIRepository
|
||||
name: cozystack-packages
|
||||
namespace: cozy-system
|
||||
path: /
|
||||
variants:
|
||||
- name: kubevirt
|
||||
dependsOn:
|
||||
- cozystack.networking
|
||||
- cozystack.kubevirt
|
||||
- cozystack.kubevirt-cdi
|
||||
libraries:
|
||||
- name: cozy-lib
|
||||
path: library/cozy-lib
|
||||
components:
|
||||
- name: virtual-machine
|
||||
path: apps/virtual-machine
|
||||
libraries: [cozy-lib]
|
||||
- name: virtual-machine-rd
|
||||
path: system/virtual-machine-rd
|
||||
install:
|
||||
namespace: cozy-system
|
||||
releaseName: virtual-machine-rd
|
||||
@@ -13,7 +13,6 @@
|
||||
{{include "cozystack.platform.package.default" (list "cozystack.capi-provider-infra-kubevirt" $) }}
|
||||
{{include "cozystack.platform.package.default" (list "cozystack.bucket-application" $) }}
|
||||
{{include "cozystack.platform.package" (list "cozystack.kubernetes-application" "kubevirt" $) }}
|
||||
{{include "cozystack.platform.package" (list "cozystack.virtual-machine-application" "kubevirt" $) }}
|
||||
{{include "cozystack.platform.package" (list "cozystack.virtualprivatecloud-application" "kubevirt" $) }}
|
||||
{{include "cozystack.platform.package" (list "cozystack.vm-disk-application" "kubevirt" $) }}
|
||||
{{include "cozystack.platform.package" (list "cozystack.vm-instance-application" "kubevirt" $) }}
|
||||
|
||||
@@ -6,7 +6,7 @@ sourceRef:
|
||||
migrations:
|
||||
enabled: false
|
||||
image: ghcr.io/cozystack/cozystack/platform-migrations:latest
|
||||
targetVersion: 29
|
||||
targetVersion: 30
|
||||
# Bundle deployment configuration
|
||||
bundles:
|
||||
system:
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
apiVersion: v2
|
||||
name: virtual-machine-rd
|
||||
version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process
|
||||
@@ -1,4 +0,0 @@
|
||||
export NAME=virtual-machine-rd
|
||||
export NAMESPACE=cozy-system
|
||||
|
||||
include ../../../hack/package.mk
|
||||
@@ -1,37 +0,0 @@
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: ApplicationDefinition
|
||||
metadata:
|
||||
name: virtual-machine
|
||||
spec:
|
||||
application:
|
||||
kind: VirtualMachine
|
||||
plural: virtualmachines
|
||||
singular: virtualmachine
|
||||
openAPISchema: |-
|
||||
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"cpuModel":{"description":"Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"runStrategy":{"description":"Requested running state of the VirtualMachineInstance","type":"string","default":"Always","enum":["Always","Halted","Manual","RerunOnFailure","Once"]},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}}
|
||||
release:
|
||||
prefix: virtual-machine-
|
||||
labels:
|
||||
sharding.fluxcd.io/key: tenants
|
||||
chartRef:
|
||||
kind: ExternalArtifact
|
||||
name: cozystack-virtual-machine-application-kubevirt-virtual-machine
|
||||
namespace: cozy-system
|
||||
dashboard:
|
||||
category: IaaS
|
||||
singular: Virtual Machine
|
||||
plural: Virtual Machines
|
||||
description: Virtual Machine (simple)
|
||||
weight: 10
|
||||
tags:
|
||||
- compute
|
||||
icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQ1NCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDU0KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMDBCM0ZGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi41Mjc1IDUzLjgzNjdDNzIuNDQzMSA1My44MzUxIDcyLjM2MDUgNTMuODEyMiA3Mi4yODczIDUzLjc3MDFMNTYuNDY5OSA0NC43OTM0QzU2LjM5ODMgNDQuNzUxOSA1Ni4zMzg4IDQ0LjY5MjMgNTYuMjk3MyA0NC42MjA3QzU2LjI1NTkgNDQuNTQ5IDU2LjIzMzggNDQuNDY3OCA1Ni4yMzM0IDQ0LjM4NUM1Ni4yMzM0IDQ0LjIxNjkgNTYuMzI1OCA0NC4wNjE3IDU2LjQ2OTkgNDMuOTc4NUw3Mi4xOTEyIDM1LjA2MDlDNzIuMjYzNyAzNS4wMjEgNzIuMzQ1IDM1IDcyLjQyNzcgMzVDNzIuNTEwNSAzNSA3Mi41OTE4IDM1LjAyMSA3Mi42NjQzIDM1LjA2MDlMODguNDg3MiA0NC4wMzk1Qzg4LjU1OTEgNDQuMDgwMSA4OC42MTg4IDQ0LjEzOTIgODguNjYgNDQuMjEwN0M4OC43MDEzIDQ0LjI4MjIgODguNzIyNyA0NC4zNjM1IDg4LjcyMTkgNDQuNDQ2Qzg4LjcyMjUgNDQuNTI4NSA4OC43MDEgNDQuNjA5NyA4OC42NTk4IDQ0LjY4MTJDODguNjE4NSA0NC43NTI2IDg4LjU1ODkgNDQuODExOCA4OC40ODcyIDQ0Ljg1MjVMNzIuNzcxNCA1My43NjgzQzcyLjY5NzIgNTMuODExNCA3Mi42MTMzIDUzLjgzNDkgNzIuNTI3NSA1My44MzY3IiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03MC4yNTUzIDc1LjY1MTdDNzAuMTcxIDc1LjY1MzUgNzAuMDg3OCA3NS42MzE3IDcwLjAxNTEgNzUuNTg4OEw1NC4yNDU4IDY2LjY0MTdDNTQuMTcxNSA2Ni42MDI0IDU0LjEwOTUgNjYuNTQzNiA1NC4wNjYxIDY2LjQ3MTZDNTQuMDIyOCA2Ni4zOTk3IDU0IDY2LjMxNzMgNTQgNjYuMjMzM1Y0OC4yNzhDNTQgNDguMTA4IDU0LjA5MjQgNDcuOTU0NiA1NC4yNDM5IDQ3Ljg2OTZDNTQuMzE3MiA0Ny44MjcxIDU0LjQwMDQgNDcuODA0NyA1NC40ODUxIDQ3LjgwNDdDNTQuNTY5NyA0Ny44MDQ3IDU0LjY1MjkgNDcuODI3MSA1NC43MjYyIDQ3Ljg2OTZMNzAuNDkzNyA1Ni44MTMxQzcwLjU2NDIgNTYuODU2NSA3MC42MjI1IDU2LjkxNyA3MC42NjMyIDU2Ljk4OTFDNzAuNzAzOSA1Ny4wNjEyIDcwLjcyNTcgNTcuMTQyNCA3MC43MjY1IDU3LjIyNTFWNzUuMTgwNUM3MC43MjU5IDc1LjI2MjggNzAuNzA0MiA3NS4zNDM2IDcwLjY2MzUgNzUuNDE1MUM3MC42MjI3IDc1LjQ4NjYgNzAuNTY0MiA3NS41NDY0IDcwLjQ5MzcgNzUuNTg4OEM3MC40MjA2IDc1LjYyOTEgNzAuMzM4NyA3NS42NTA3IDcwLjI1NTMgNzUuNjUxNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggb3BhY2l0eT0iMC40IiBkPSJNNzQuNzE5OCA3NS42NTExQzc0LjYzMzMgNzUuNjUxMiA3NC41NDgyIDc1LjYyOTYgNzQuNDcyMiA3NS41ODgzQzc0LjQwMTYgNzUuNTQ2MSA3NC4zNDMyIDc1LjQ4NjIgNzQuMzAyNyA3NS40MTQ3Qzc0LjI2MjMgNzUuMzQzMSA3NC4yNDExIDc1LjI2MjIgNzQuMjQxMiA3NS4xOFY1Ny4zMzczQzc0LjI0MTIgNTcuMTcxIDc0LjMzMzYgNTcuMDE1OCA3NC40NzIyIDU2LjkyOUw5MC4yMzk3IDQ3Ljk4NTVDOTAuMzExOSA0Ny45NDM4IDkwLjM5MzggNDcuOTIxOSA5MC40NzcxIDQ3LjkyMTlDOTAuNTYwNSA0Ny45MjE5IDkwLjY0MjQgNDcuOTQzOCA5MC43MTQ2IDQ3Ljk4NTVDOTAuNzg3NiA0OC4wMjU1IDkwLjg0ODUgNDguMDg0MiA5MC44OTExIDQ4LjE1NTdDOTAuOTMzNyA0OC4yMjcyIDkwLjk1NjMgNDguMzA4OCA5MC45NTY2IDQ4LjM5MlY2Ni4yMzI4QzkwLjk1NyA2Ni4zMTY0IDkwLjkzNDcgNjYuMzk4NSA5MC44OTIxIDY2LjQ3MDRDOTAuODQ5NSA2Ni41NDI0IDkwLjc4ODEgNjYuNjAxNCA5MC43MTQ2IDY2LjY0MTFMNzQuOTUyNiA3NS41ODgzQzc0Ljg4MjUgNzUuNjMwNyA3NC44MDE4IDc1LjY1MjUgNzQuNzE5OCA3NS42NTExIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zNDU0IiB4MT0iMTYxIiB5MT0iMTgwIiB4Mj0iMy41OTI4NGUtMDciIHkyPSI0Ljk5OTk4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTk1NjU2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjg3XzM0NTQiPgo8cmVjdCB3aWR0aD0iOTQiIGhlaWdodD0iOTQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNSAyNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K
|
||||
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "runStrategy"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "systemDisk"], ["spec", "systemDisk", "image"], ["spec", "systemDisk", "storage"], ["spec", "systemDisk", "storageClass"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "cpuModel"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
|
||||
secrets:
|
||||
exclude: []
|
||||
include: []
|
||||
services:
|
||||
exclude: []
|
||||
include:
|
||||
- resourceNames:
|
||||
- virtual-machine-{{ .name }}
|
||||
@@ -1,35 +0,0 @@
|
||||
{{- if .Capabilities.APIVersions.Has "strategy.backups.cozystack.io/v1alpha1" }}
|
||||
apiVersion: strategy.backups.cozystack.io/v1alpha1
|
||||
kind: Velero
|
||||
metadata:
|
||||
name: velero-backup-strategy-for-virtualmachines
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
csiSnapshotTimeout: 10m0s
|
||||
includedNamespaces:
|
||||
- '{{ printf "{{ .Application.metadata.namespace }}" }}'
|
||||
orLabelSelector:
|
||||
- matchLabels:
|
||||
apps.cozystack.io/application.group: apps.cozystack.io
|
||||
apps.cozystack.io/application.kind: '{{ printf "{{ .Application.kind }}" }}'
|
||||
apps.cozystack.io/application.name: '{{ printf "{{ .Application.metadata.name }}"}}'
|
||||
- matchLabels:
|
||||
app.kubernetes.io/instance: 'virtual-machine-{{ printf "{{ .Application.metadata.name }}"}}'
|
||||
includedResources:
|
||||
- helmreleases.helm.toolkit.fluxcd.io
|
||||
- virtualmachines.kubevirt.io
|
||||
- virtualmachineinstances.kubevirt.io
|
||||
- datavolumes.cdi.kubevirt.io
|
||||
- persistentvolumeclaims
|
||||
- services
|
||||
- configmaps
|
||||
- secrets
|
||||
storageLocation: '{{ printf "{{ .Parameters.backupStorageLocationName }}" }}'
|
||||
volumeSnapshotLocations:
|
||||
- '{{ printf "{{ .Parameters.backupStorageLocationName }}" }}'
|
||||
snapshotVolumes: true
|
||||
snapshotMoveData: true
|
||||
ttl: 720h0m0s
|
||||
itemOperationTimeout: 24h0m0s
|
||||
{{- end }}
|
||||
@@ -1,4 +0,0 @@
|
||||
{{- range $path, $_ := .Files.Glob "cozyrds/*" }}
|
||||
---
|
||||
{{ $.Files.Get $path }}
|
||||
{{- end }}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -8,7 +8,7 @@ spec:
|
||||
singular: vminstance
|
||||
plural: vminstances
|
||||
openAPISchema: |-
|
||||
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"running":{"description":"Determines if the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}}}}
|
||||
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"cpuModel":{"description":"Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map","type":"string","default":""},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"runStrategy":{"description":"Requested running state of the VirtualMachineInstance","type":"string","default":"Always","enum":["Always","Halted","Manual","RerunOnFailure","Once"]},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}}}}
|
||||
release:
|
||||
prefix: vm-instance-
|
||||
labels:
|
||||
@@ -26,7 +26,7 @@ spec:
|
||||
tags:
|
||||
- compute
|
||||
icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQ1NCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDU0KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMDBCM0ZGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi41Mjc1IDUzLjgzNjdDNzIuNDQzMSA1My44MzUxIDcyLjM2MDUgNTMuODEyMiA3Mi4yODczIDUzLjc3MDFMNTYuNDY5OSA0NC43OTM0QzU2LjM5ODMgNDQuNzUxOSA1Ni4zMzg4IDQ0LjY5MjMgNTYuMjk3MyA0NC42MjA3QzU2LjI1NTkgNDQuNTQ5IDU2LjIzMzggNDQuNDY3OCA1Ni4yMzM0IDQ0LjM4NUM1Ni4yMzM0IDQ0LjIxNjkgNTYuMzI1OCA0NC4wNjE3IDU2LjQ2OTkgNDMuOTc4NUw3Mi4xOTEyIDM1LjA2MDlDNzIuMjYzNyAzNS4wMjEgNzIuMzQ1IDM1IDcyLjQyNzcgMzVDNzIuNTEwNSAzNSA3Mi41OTE4IDM1LjAyMSA3Mi42NjQzIDM1LjA2MDlMODguNDg3MiA0NC4wMzk1Qzg4LjU1OTEgNDQuMDgwMSA4OC42MTg4IDQ0LjEzOTIgODguNjYgNDQuMjEwN0M4OC43MDEzIDQ0LjI4MjIgODguNzIyNyA0NC4zNjM1IDg4LjcyMTkgNDQuNDQ2Qzg4LjcyMjUgNDQuNTI4NSA4OC43MDEgNDQuNjA5NyA4OC42NTk4IDQ0LjY4MTJDODguNjE4NSA0NC43NTI2IDg4LjU1ODkgNDQuODExOCA4OC40ODcyIDQ0Ljg1MjVMNzIuNzcxNCA1My43NjgzQzcyLjY5NzIgNTMuODExNCA3Mi42MTMzIDUzLjgzNDkgNzIuNTI3NSA1My44MzY3IiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03MC4yNTUzIDc1LjY1MTdDNzAuMTcxIDc1LjY1MzUgNzAuMDg3OCA3NS42MzE3IDcwLjAxNTEgNzUuNTg4OEw1NC4yNDU4IDY2LjY0MTdDNTQuMTcxNSA2Ni42MDI0IDU0LjEwOTUgNjYuNTQzNiA1NC4wNjYxIDY2LjQ3MTZDNTQuMDIyOCA2Ni4zOTk3IDU0IDY2LjMxNzMgNTQgNjYuMjMzM1Y0OC4yNzhDNTQgNDguMTA4IDU0LjA5MjQgNDcuOTU0NiA1NC4yNDM5IDQ3Ljg2OTZDNTQuMzE3MiA0Ny44MjcxIDU0LjQwMDQgNDcuODA0NyA1NC40ODUxIDQ3LjgwNDdDNTQuNTY5NyA0Ny44MDQ3IDU0LjY1MjkgNDcuODI3MSA1NC43MjYyIDQ3Ljg2OTZMNzAuNDkzNyA1Ni44MTMxQzcwLjU2NDIgNTYuODU2NSA3MC42MjI1IDU2LjkxNyA3MC42NjMyIDU2Ljk4OTFDNzAuNzAzOSA1Ny4wNjEyIDcwLjcyNTcgNTcuMTQyNCA3MC43MjY1IDU3LjIyNTFWNzUuMTgwNUM3MC43MjU5IDc1LjI2MjggNzAuNzA0MiA3NS4zNDM2IDcwLjY2MzUgNzUuNDE1MUM3MC42MjI3IDc1LjQ4NjYgNzAuNTY0MiA3NS41NDY0IDcwLjQ5MzcgNzUuNTg4OEM3MC40MjA2IDc1LjYyOTEgNzAuMzM4NyA3NS42NTA3IDcwLjI1NTMgNzUuNjUxNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggb3BhY2l0eT0iMC40IiBkPSJNNzQuNzE5OCA3NS42NTExQzc0LjYzMzMgNzUuNjUxMiA3NC41NDgyIDc1LjYyOTYgNzQuNDcyMiA3NS41ODgzQzc0LjQwMTYgNzUuNTQ2MSA3NC4zNDMyIDc1LjQ4NjIgNzQuMzAyNyA3NS40MTQ3Qzc0LjI2MjMgNzUuMzQzMSA3NC4yNDExIDc1LjI2MjIgNzQuMjQxMiA3NS4xOFY1Ny4zMzczQzc0LjI0MTIgNTcuMTcxIDc0LjMzMzYgNTcuMDE1OCA3NC40NzIyIDU2LjkyOUw5MC4yMzk3IDQ3Ljk4NTVDOTAuMzExOSA0Ny45NDM4IDkwLjM5MzggNDcuOTIxOSA5MC40NzcxIDQ3LjkyMTlDOTAuNTYwNSA0Ny45MjE5IDkwLjY0MjQgNDcuOTQzOCA5MC43MTQ2IDQ3Ljk4NTVDOTAuNzg3NiA0OC4wMjU1IDkwLjg0ODUgNDguMDg0MiA5MC44OTExIDQ4LjE1NTdDOTAuOTMzNyA0OC4yMjcyIDkwLjk1NjMgNDguMzA4OCA5MC45NTY2IDQ4LjM5MlY2Ni4yMzI4QzkwLjk1NyA2Ni4zMTY0IDkwLjkzNDcgNjYuMzk4NSA5MC44OTIxIDY2LjQ3MDRDOTAuODQ5NSA2Ni41NDI0IDkwLjc4ODEgNjYuNjAxNCA5MC43MTQ2IDY2LjY0MTFMNzQuOTUyNiA3NS41ODgzQzc0Ljg4MjUgNzUuNjMwNyA3NC44MDE4IDc1LjY1MjUgNzQuNzE5OCA3NS42NTExIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zNDU0IiB4MT0iMTYxIiB5MT0iMTgwIiB4Mj0iMy41OTI4NGUtMDciIHkyPSI0Ljk5OTk4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTk1NjU2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjg3XzM0NTQiPgo8cmVjdCB3aWR0aD0iOTQiIGhlaWdodD0iOTQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNSAyNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K
|
||||
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "running"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
|
||||
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "runStrategy"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "cpuModel"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
|
||||
secrets:
|
||||
exclude: []
|
||||
include: []
|
||||
|
||||
Reference in new issue
Block a user