├── .github └── workflows │ └── build.yaml ├── .gitignore ├── Changes.md ├── Dockerfile ├── LICENSE ├── Readme.md ├── build.sh ├── qemu-ga-talos-with-sa.yaml ├── qemu-ga-talos.yaml └── scripts ├── service.sh └── shutdown /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | # based on example from 2 | # - https://docs.docker.com/build/ci/github-actions/examples/ 3 | # - https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows 4 | # 5 | # See also: 6 | # - https://github.com/actions/checkout 7 | # - https://github.com/actions/cache 8 | # - https://github.com/docker/setup-buildx-action 9 | # 10 | name: build 11 | 12 | on: 13 | push: 14 | branches: 15 | - '**' 16 | tags-ignore: 17 | - '**' 18 | # ignore github repo settings and all readme adoc changes 19 | paths-ignore: 20 | - '.github/*.yml' 21 | - '**/*.adoc' 22 | - '**/*.md' 23 | 24 | # once every week sunday 03:22 25 | schedule: 26 | - cron: '22 03 * * 0' 27 | pull_request: 28 | workflow_dispatch: 29 | 30 | env: 31 | IMAGE-PATH: ${{ github.repository }} 32 | IMAGE-REGISTRY: ghcr.io 33 | defaults: 34 | run: 35 | shell: bash 36 | 37 | jobs: 38 | build: 39 | runs-on: ubuntu-latest 40 | 41 | steps: 42 | - name: Git Checkout 43 | uses: actions/checkout@v3 44 | 45 | - name: Set up QEMU 46 | uses: docker/setup-qemu-action@v2 47 | # https://github.com/docker/setup-buildx-action 48 | 49 | - name: Set up Docker Buildx 50 | id: buildx 51 | uses: docker/setup-buildx-action@v2 52 | 53 | - name: Docker meta 54 | id: my_meta # you'll use this in the next step 55 | uses: docker/metadata-action@v4 56 | with: 57 | # list of Docker images to use as base name for tags 58 | images: | 59 | ${{ env.IMAGE-REGISTRY }}/${{ env.IMAGE-PATH }} 60 | # Docker tags based on the following events/attributes 61 | tags: | 62 | type=schedule 63 | type=ref,event=branch 64 | type=ref,event=pr 65 | type=semver,pattern={{version}} 66 | type=semver,pattern={{major}}.{{minor}} 67 | type=semver,pattern={{major}} 68 | type=sha 69 | - name: Login to docker repo 70 | uses: docker/login-action@v2 71 | with: 72 | username: ${{ github.actor }} 73 | password: ${{ secrets.GITHUB_TOKEN }} 74 | registry: ${{ env.IMAGE-REGISTRY}} 75 | - name: Build and push 76 | uses: docker/build-push-action@v2 77 | with: 78 | context: . 79 | platforms: linux/amd64 80 | push: ${{ github.event_name != 'pull_request' }} 81 | tags: ${{ steps.my_meta.outputs.tags }} 82 | labels: ${{ steps.my_meta.outputs.labels }} 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crisobal/qemu-guest-agent-talos/11981b6e0575771038142308beb928c77451618a/.gitignore -------------------------------------------------------------------------------- /Changes.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | ## v0.5.0 3 | __Fiexes:__ 4 | - Typos in readme 5 | - Build Status in readme 6 | __New:__ 7 | - Update talosctl to v1.6.7 8 | __Breaking Changes:__ 9 | - Remove namespace creation from qemu-ga-talos.yaml so you can use kubectl create/delete -f ...yaml without killing your secrets 10 | 11 | ## v0.0.3 12 | __Fixes:__ 13 | - Fixed Logging, so actions initiated by hypervisor are correctly logged 14 | - Update talosctrl to 1.3.5 15 | 16 | ## v0.0.2 and v0.0.1 17 | First at least working versions. 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | LABEL org.opencontainers.image.authors="Crispin Tschirky " 4 | LABEL description="Provides Qemu Guest Agent using talosctl to shutdown/reboot host node. Intended as daemonset for talos running on proxmox" 5 | LABEL org.opencontainers.image.description="Provides Qemu Guest Agent using talosctl to shutdown/reboot host node. Intended as daemonset for talos running on proxmox" 6 | 7 | COPY ./scripts/shutdown /sbin/shutdown 8 | COPY ./scripts/service.sh /usr/local/bin/service.sh 9 | 10 | RUN apk --no-cache add qemu-guest-agent && \ 11 | echo "$arch" && \ 12 | wget https://github.com/siderolabs/talos/releases/download/v1.6.7/talosctl-linux-amd64 && \ 13 | mv talosctl-linux-amd64 /usr/bin/talosctl && \ 14 | chmod 755 /usr/bin/talosctl && \ 15 | touch /var/log/qemu-ga.log && \ 16 | chmod 777 /var/log/qemu-ga.log 17 | 18 | USER root 19 | 20 | ENTRYPOINT ["/usr/local/bin/service.sh"] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Crispin Tschirky 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | [![build](https://github.com/crisobal/qemu-guest-agent-talos/actions/workflows/build.yaml/badge.svg)](https://github.com/crisobal/qemu-guest-agent-talos/actions/workflows/build.yaml) 2 | 3 | # Motivation 4 | Virtual machines running on proxmox should have the qemu-guest-agent installed as it improves the way virtual machines are restarted as it does not simply rely on virtualized ACPI commands. Additionally `qemu-guest-agent` provides information like network addresses. Talos linux does not feature the `qemu-quest-agent`. 5 | 6 | # Description 7 | The `qemu-guest-agent` can run as daemonset in kubernetes but id does not allow to reboot or shutdown a node when using e.g. proxmox as infrastructure. As proxmox sends all the machine run state events like reboot and shutdown to the agent the daemon pod must be capable to restart the host. This can be achieved by using `talosctl` to restart the host node. For `talsoctl` talos config is required to have the api access to restart the host node. 8 | 9 | # Prerequisits 10 | 11 | ## Enable Guest Agent for node vm 12 | 13 | Qemu / KVM must have the guest agent enabled for node virtual machine. In Proxmox you find this setting under virtual machine options. 14 | 15 | 16 | ## Secret with talosconfig 17 | 18 | To have the `talosctl` fully functional you need a talos config. This config must be injected either as secret or as service account. The advantage of a service account is, that you do not need to create the config itself. Unfortunately the privileges required to reboot or shutdown a node is `os:admin`. As talos config only allows setting the allowed permissions (`os:read`, `os:admin`) but lacks a way to set this per namespace, this option is not recommended. If you need the talos config only in the `qemu-guest-agent` namespace it would be ok to grant os:admin for this namespace, but for arbitrary namespaces `os:read` is already more than enough. 19 | If you want the version with the service account use the `qemu-guest-agent-with-sa.yaml`. Ensure that you have the right permissions granted in your node talos machine config (machine.features.kubernetesTalosAPIAccess). 20 | 21 | Otherwise just create the secret using: 22 | 23 | ``` 24 | k create secret -n qemu-guest-agent generic talosconfig --from-file=config= 25 | ``` 26 | 27 | ## Admission controller security 28 | As the container runs as root and uses a hostFolder mount for the virtio device, it requires either the PodSecurity being set very low or better switch it of for the `qemu-quest-agent` namespace. 29 | ``` 30 | admissionControl: 31 | - name: PodSecurity 32 | configuration: 33 | apiVersion: pod-security.admission.config.k8s.io/v1alpha1 34 | defaults: 35 | audit: restricted 36 | audit-version: latest 37 | enforce: baseline 38 | enforce-version: latest 39 | warn: restricted 40 | warn-version: latest 41 | exemptions: 42 | namespaces: 43 | - kube-system 44 | - qemu-guest-agent 45 | runtimeClasses: [] 46 | usernames: [] 47 | kind: PodSecurityConfiguratio 48 | ``` 49 | 50 | Set this in your control plane config using `talosctl`. 51 | 52 | 53 | ## Pull Secret (only for private registries) 54 | ----------- 55 | Create image pull secret with your docker or podman `auth.json` / `client.json`. This is only required in case you pull from a private registry 56 | 57 | ``` 58 | kubectl create secret generic regcred \ 59 | --from-file=.dockerconfigjson=/run/user/.../containers/auth.json \ 60 | --type=kubernetes.io/dockerconfigjson --namespace qemu-guest-agent 61 | ``` 62 | 63 | 64 | # Installation 65 | 66 | Install using: 67 | ``` 68 | kubectl create -f qemu-ga-talos.yaml 69 | ``` 70 | 71 | Uninstall using: 72 | ``` 73 | kubectl delete -f qemu-ga-talos.yaml 74 | ``` 75 | 76 | # License and Acknowledgement 77 | This code is provided under the BSD-3 license. 78 | 79 | The following external dependencies are used: 80 | - alpine Linux as base for container 81 | - `qemu-guest-agent` inside the container 82 | - `talosctl` to talk to the talos host 83 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IMAGE="qemu-guest-agent-talos" 4 | VERSION="0.5.0" 5 | 6 | REPO="ghcr.io/crisobal" 7 | 8 | tag(){ 9 | SRC=$1 10 | TGT=$2 11 | echo "" 12 | echo "Tag image: ${SRC} -> ${TGT}" 13 | podman tag ${SRC} ${TGT} 14 | } 15 | 16 | push(){ 17 | TGT=$1 18 | echo "" 19 | echo "Push to: ${TGT}" 20 | podman push ${TGT} 21 | } 22 | 23 | release(){ 24 | SRC=$IMAGE:$VERSION 25 | TGT=$1/${IMAGE}:$2 26 | tag ${SRC} ${TGT} 27 | push ${TGT} 28 | } 29 | 30 | podman build -t "${IMAGE}:${VERSION}" . 31 | 32 | 33 | release ${REPO} ${VERSION} 34 | release ${REPO} "latest" 35 | -------------------------------------------------------------------------------- /qemu-ga-talos-with-sa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: qemu-guest-agent 5 | --- 6 | apiVersion: apps/v1 7 | kind: DaemonSet 8 | metadata: 9 | creationTimestamp: null 10 | name: qemu-ga-talos 11 | namespace: qemu-guest-agent 12 | spec: 13 | selector: 14 | matchLabels: 15 | name: qemu-ga-talos 16 | template: 17 | metadata: 18 | creationTimestamp: null 19 | labels: 20 | name: qemu-ga-talos 21 | spec: 22 | containers: 23 | - image: ghcr.io/crisobal/qemu-guest-agent-talos:0.5.0 24 | imagePullPolicy: IfNotPresent 25 | name: qemu-ga-talos 26 | resources: {} 27 | securityContext: 28 | privileged: true 29 | volumeMounts: 30 | - mountPath: /dev/virtio-ports 31 | name: virtio 32 | - mountPath: /etc/talos 33 | name: talosconfig 34 | readOnly: true 35 | - mountPath: /var/run/secrets/talos.dev 36 | name: talos-secrets 37 | dnsPolicy: ClusterFirst 38 | hostNetwork: true 39 | restartPolicy: Always 40 | schedulerName: default-scheduler 41 | securityContext: {} 42 | terminationGracePeriodSeconds: 30 43 | tolerations: 44 | - effect: NoSchedule 45 | key: node-role.kubernetes.io/control-plane 46 | operator: Exists 47 | - effect: NoSchedule 48 | key: node-role.kubernetes.io/master 49 | - key: CriticalAddonsOnly 50 | operator: Exists 51 | - effect: NoExecute 52 | key: node.kubernetes.io/not-ready 53 | operator: Exists 54 | tolerationSeconds: 120 55 | - effect: NoExecute 56 | key: node.kubernetes.io/unreachable 57 | operator: Exists 58 | tolerationSeconds: 120 59 | - effect: NoSchedule 60 | key: node.kubernetes.io/memory-pressure 61 | operator: Exists 62 | volumes: 63 | - hostPath: 64 | path: /dev/virtio-ports 65 | type: "" 66 | name: virtio 67 | - name: talosconfig 68 | secret: 69 | optional: true 70 | secretName: talosconfig 71 | - name: talos-secrets 72 | secret: 73 | secretName: qemu-guest-agent-talos-secrets 74 | updateStrategy: {} 75 | status: 76 | currentNumberScheduled: 0 77 | desiredNumberScheduled: 0 78 | numberMisscheduled: 0 79 | numberReady: 0 80 | --- 81 | apiVersion: talos.dev/v1alpha1 82 | kind: ServiceAccount 83 | metadata: 84 | name: qemu-guest-agent-talos-secrets 85 | namespace: qemu-guest-agent 86 | spec: 87 | roles: 88 | - os:admin 89 | --- 90 | 91 | -------------------------------------------------------------------------------- /qemu-ga-talos.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: qemu-ga-talos 5 | namespace: qemu-guest-agent 6 | spec: 7 | selector: 8 | matchLabels: 9 | name: qemu-ga-talos 10 | template: 11 | metadata: 12 | labels: 13 | name: qemu-ga-talos 14 | spec: 15 | volumes: 16 | - name: virtio 17 | hostPath: 18 | path: /dev/virtio-ports 19 | type: '' 20 | - name: talosconfig 21 | secret: 22 | secretName: talosconfig 23 | optional: false 24 | containers: 25 | - name: qemu-ga-talos 26 | image: ghcr.io/crisobal/qemu-guest-agent-talos:0.5.0 27 | imagePullPolicy: IfNotPresent 28 | securityContext: 29 | privileged: true 30 | allowPrivilegeEscalation: true 31 | volumeMounts: 32 | - name: virtio 33 | mountPath: /dev/virtio-ports 34 | - name: talosconfig 35 | mountPath: /var/run/secrets/talos.dev 36 | readOnly: true 37 | restartPolicy: Always 38 | hostNetwork: true 39 | terminationGracePeriodSeconds: 30 40 | dnsPolicy: ClusterFirst 41 | securityContext: {} 42 | schedulerName: default-scheduler 43 | tolerations: 44 | - key: node-role.kubernetes.io/control-plane 45 | effect: NoSchedule 46 | operator: Exists 47 | - key: node-role.kubernetes.io/master 48 | effect: NoSchedule 49 | - key: CriticalAddonsOnly 50 | operator: Exists 51 | - key: node.kubernetes.io/not-ready 52 | operator: Exists 53 | effect: NoExecute 54 | tolerationSeconds: 120 55 | - key: node.kubernetes.io/unreachable 56 | operator: Exists 57 | effect: NoExecute 58 | tolerationSeconds: 120 59 | - key: node.kubernetes.io/memory-pressure 60 | operator: Exists 61 | effect: NoSchedule 62 | -------------------------------------------------------------------------------- /scripts/service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # (c) 2023 by Crispin Tschirky 3 | # 4 | # Licensed under BSD-3 License, see https://opensource.org/licenses/BSD-3-Clause 5 | # 6 | # Replacement for shutdown on pod which invokes the operation on the real host 7 | # can be used for qemu-guest-agent daemon pod to reboot the host in case e.g. proxmox requests it 8 | # 9 | 10 | LOGFILE=/var/log/qemu-ga.log 11 | 12 | ln -sf /proc/1/fd/1 $LOGFILE 13 | 14 | echo "Startup qemu-guest-agent" > $LOGFILE 15 | 16 | qemu-ga -l $LOGFILE 17 | -------------------------------------------------------------------------------- /scripts/shutdown: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # (c) 2023 by Crispin Tschirky 3 | # 4 | # Licensed under BSD-3 License, see https://opensource.org/licenses/BSD-3-Clause 5 | # 6 | # Replacement for shutdown on pod which invokes the operation on the real host 7 | # can be used for qemu-guest-agent daemon pod to reboot the host in case e.g. proxmox requests it 8 | # 9 | 10 | LOGFILE=/var/log/qemu-ga.log 11 | 12 | logw(){ 13 | echo [`date -u -Iseconds`] [WARN] [$HOSTNAME] - "$1" > $LOGFILE 14 | } 15 | 16 | logi(){ 17 | echo [`date -u -Iseconds`] [INFO] [$HOSTNAME] - "$1" > $LOGFILE 18 | } 19 | 20 | T="/usr/bin/talosctl -n $HOSTNAME -e $HOSTNAME " 21 | 22 | logi "Invoke: $0 $*" 23 | 24 | for i in "$@"; do 25 | case $i in 26 | -r) 27 | logw "Invoke REBOOT" 28 | $T reboot 29 | shift # past argument=value 30 | ;; 31 | -H) 32 | logw "Invoke SHUTDOWN" 33 | $T shutdown 34 | shift # past argument=value 35 | ;; 36 | -h) 37 | shift # past argument=value 38 | ;; 39 | -P) 40 | logw "Invoke SHUTDOWN" 41 | $T shutdown 42 | shift # past argument=value 43 | ;; 44 | -*) 45 | exit 1 46 | ;; 47 | *) 48 | #echo "Ignore Duration $i" 49 | ;; 50 | esac 51 | done 52 | --------------------------------------------------------------------------------