Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions chart/pelican-wings/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: v2
name: pelican-wings
description: Helm chart for Pelican Wings — the game server management daemon with Kubernetes support
type: application
version: 0.1.0
appVersion: "1.0.0"
version: 1.0.2
appVersion: "1.0.2"
keywords:
- pelican
- wings
Expand Down
65 changes: 56 additions & 9 deletions chart/pelican-wings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@ networking support.

## Quick Start

Put your node credentials from the Panel in a local values file (kept out of
version control) rather than on the command line, where `--set` would leak them
into shell history and process listings:

```yaml
# values.local.yaml (do not commit)
wings:
panelUrl: https://panel.example.com
token: YOUR_TOKEN
tokenId: YOUR_TOKEN_ID
uuid: YOUR_NODE_UUID
```

```bash
# Add your node credentials from the Panel
helm install wings ./chart/pelican-wings \
--set wings.panelUrl=https://panel.example.com \
--set wings.token=YOUR_TOKEN \
--set wings.tokenId=YOUR_TOKEN_ID \
--set wings.uuid=YOUR_NODE_UUID
helm install wings ./chart/pelican-wings -f values.local.yaml
```

## Configuration
Expand All @@ -33,13 +41,26 @@ See [values.yaml](values.yaml) for the full list of configurable values.
| `wings.token` | Panel authentication token | `""` |
| `wings.tokenId` | Panel token ID | `""` |
| `wings.uuid` | Node UUID from Panel | `""` |
| `wings.kubernetes.networkMode` | Port exposure: `hostport` or `nodeport` | `nodeport` |
| `wings.kubernetes.networkMode` | Port exposure: `hostport`, `nodeport`, or `loadbalancer` | `nodeport` |
| `wings.kubernetes.storageMode` | Storage: `hostpath` or `pvc` | `pvc` |
| `wings.kubernetes.storageClass` | StorageClass for PVCs | `""` (cluster default) |
| `wings.kubernetes.storageSize` | Default PVC size | `10Gi` |
| `wings.kubernetes.imagePullPolicy` | Pull policy for game server Pods/install Jobs: `Always`, `IfNotPresent`, `Never` | `""` (smart default) |
| `gameNamespace` | Namespace for game server resources | `pelican` |
| `rbac.create` | Create RBAC resources | `true` |
| `rbac.kubeletMetricsFallback` | Grant cluster-wide `nodes/proxy` for the kubelet stats fallback (broad permission; prefer metrics-server) | `false` |
| `serviceAccount.create` | Create ServiceAccount | `true` |
| `serviceAccount.name` | ServiceAccount name (**required** when `serviceAccount.create=false`) | `""` |

> **Namespace:** Wings schedules game-server workloads into `gameNamespace`, and
> the chart creates the namespaced RBAC there. `wings.kubernetes.namespace` is
> therefore derived from `gameNamespace`; if you set it explicitly it must match
> `gameNamespace` or the chart will fail to render.

> **Credentials:** `wings.token`, `wings.tokenId`, and `wings.uuid` are rendered
> into a Kubernetes **Secret** (not a ConfigMap). Supply them via a private
> values file or `--set`, e.g. `helm install ... -f my-creds.yaml`, and keep that
> file out of version control.

### Storage

Expand Down Expand Up @@ -75,13 +96,39 @@ wings:
networkMode: hostport
```

LoadBalancer mode provisions a `Service` of type `LoadBalancer` per game server
(for use with MetalLB, Cilium LB-IPAM, or a cloud LB). LB IP/sharing-key
annotations can be auto-populated from the allocation IP:

```yaml
wings:
kubernetes:
networkMode: loadbalancer
```

### Image pulling

Game server Pods and installation Jobs default to `imagePullPolicy: Always`
for remote images, so updated tags are re-pulled rather than reusing a stale
copy cached on the node (matching the Docker backend). `~`-prefixed local
images are never pulled. Override this for air-gapped clusters:

```yaml
wings:
kubernetes:
imagePullPolicy: IfNotPresent # or "Never"
```

This is independent of `image.pullPolicy`, which applies to the Wings daemon
image itself.

## What Gets Created

- **Namespace** — `pelican` (configurable)
- **ServiceAccount** — For Wings and game server Pods
- **Role + RoleBinding** — Namespace-scoped permissions (Pods, Services, Jobs, PVCs)
- **ClusterRole + ClusterRoleBinding** — Metrics API access
- **ConfigMap** — Wings configuration file
- **ClusterRole + ClusterRoleBinding** — Metrics API access (`nodes/proxy` only when `rbac.kubeletMetricsFallback=true`)
- **Secret** — Wings configuration file (contains Panel token)
- **Deployment** — Wings daemon with health probes
- **Service** — Exposes Wings API within the cluster

Expand Down
4 changes: 3 additions & 1 deletion chart/pelican-wings/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ Create the name of the service account to use.
{{- define "pelican-wings.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "pelican-wings.fullname" .) .Values.serviceAccount.name }}
{{- else if .Values.serviceAccount.name }}
{{- .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- fail "serviceAccount.name must be set when serviceAccount.create is false: binding RBAC to the namespace 'default' ServiceAccount would grant Wings' permissions to every workload using it." }}
{{- end }}
{{- end }}
5 changes: 4 additions & 1 deletion chart/pelican-wings/templates/clusterrole-metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get"]
{{- if .Values.rbac.kubeletMetricsFallback }}
# Proxy to kubelet stats/summary API for resource metrics when
# metrics-server is not installed.
# metrics-server is not installed. Opt-in: this is a broad cluster-scoped
# permission (rbac.kubeletMetricsFallback).
- apiGroups: [""]
resources: ["nodes/proxy"]
verbs: ["get"]
{{- end }}
{{- end }}
13 changes: 10 additions & 3 deletions chart/pelican-wings/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
{{- if and .Values.wings.kubernetes.namespace (ne .Values.wings.kubernetes.namespace .Values.gameNamespace) }}
{{- fail "wings.kubernetes.namespace must equal gameNamespace: the chart's namespaced RBAC is created in gameNamespace, so Wings must schedule game-server workloads there too. Leave wings.kubernetes.namespace empty or set it equal to gameNamespace." }}
{{- end }}
apiVersion: v1
kind: ConfigMap
kind: Secret
metadata:
name: {{ include "pelican-wings.fullname" . }}-config
namespace: {{ .Values.gameNamespace }}
labels:
{{- include "pelican-wings.labels" . | nindent 4 }}
data:
type: Opaque
stringData:
config.yml: |
debug: false
app_name: pelican
Expand All @@ -26,7 +30,7 @@ data:
tmp_directory: {{ .Values.wings.system.tmpDirectory | quote }}
kubernetes:
enabled: {{ .Values.wings.kubernetes.enabled }}
namespace: {{ .Values.wings.kubernetes.namespace | quote }}
namespace: {{ .Values.gameNamespace | quote }}
network_mode: {{ .Values.wings.kubernetes.networkMode | quote }}
{{- if .Values.wings.kubernetes.lbAnnotations }}
lb_annotations:
Expand Down Expand Up @@ -71,3 +75,6 @@ data:
image_pull_secrets:
{{- toYaml .Values.wings.kubernetes.imagePullSecrets | nindent 8 }}
{{- end }}
{{- if .Values.wings.kubernetes.imagePullPolicy }}
image_pull_policy: {{ .Values.wings.kubernetes.imagePullPolicy | quote }}
{{- end }}
4 changes: 2 additions & 2 deletions chart/pelican-wings/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ spec:
{{- end }}
volumes:
- name: config-source
configMap:
name: {{ include "pelican-wings.fullname" . }}-config
secret:
secretName: {{ include "pelican-wings.fullname" . }}-config
- name: config
emptyDir: {}
{{- if .Values.persistence.enabled }}
Expand Down
3 changes: 3 additions & 0 deletions chart/pelican-wings/templates/limitrange.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{{- if .Values.limitRange.enabled }}
{{- if not (or .Values.limitRange.defaultCPULimit .Values.limitRange.defaultMemoryLimit .Values.limitRange.defaultCPURequest .Values.limitRange.defaultMemoryRequest .Values.limitRange.maxCPU .Values.limitRange.maxMemory) }}
{{- fail "limitRange.enabled is true but no limits are set; configure at least one of defaultCPULimit, defaultMemoryLimit, defaultCPURequest, defaultMemoryRequest, maxCPU or maxMemory (an empty LimitRange has no effect)." }}
{{- end }}
apiVersion: v1
kind: LimitRange
metadata:
Expand Down
3 changes: 3 additions & 0 deletions chart/pelican-wings/templates/resourcequota.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{{- if .Values.resourceQuota.enabled }}
{{- if not (or .Values.resourceQuota.cpuLimit .Values.resourceQuota.memoryLimit .Values.resourceQuota.cpuRequest .Values.resourceQuota.memoryRequest .Values.resourceQuota.maxPods .Values.resourceQuota.maxPVCs .Values.resourceQuota.maxStorage) }}
{{- fail "resourceQuota.enabled is true but no quota fields are set; configure at least one of cpuLimit, memoryLimit, cpuRequest, memoryRequest, maxPods, maxPVCs or maxStorage (an empty ResourceQuota would block all Pods from scheduling)." }}
{{- end }}
apiVersion: v1
kind: ResourceQuota
metadata:
Expand Down
9 changes: 9 additions & 0 deletions chart/pelican-wings/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ serviceAccount:
rbac:
# -- Whether to create RBAC resources (Role, RoleBinding, ClusterRole, ClusterRoleBinding)
create: true
# -- Grant the cluster-wide nodes/proxy permission so Wings can read resource
# metrics directly from the kubelet stats/summary API when metrics-server is
# not installed. This is a broad, cluster-scoped permission; leave it off and
# install metrics-server unless you specifically need the kubelet fallback.
kubeletMetricsFallback: false

# -- Namespace for game server resources (Pods, Services, Jobs, PVCs)
gameNamespace: pelican
Expand Down Expand Up @@ -119,6 +124,10 @@ wings:
tolerations: []
# -- Image pull secrets for game server Pods
imagePullSecrets: []
# -- Override pull policy for game server Pods and install Jobs:
# "Always", "IfNotPresent", or "Never". Empty = remote images use Always
# (re-pull updated tags, matching Docker), ~-local images use IfNotPresent.
imagePullPolicy: ""

# -- Pod-level security context for the Wings Deployment
podSecurityContext: {}
Expand Down
7 changes: 7 additions & 0 deletions config/config_kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ type KubernetesConfiguration struct {
// images in game server Pods.
ImagePullSecrets []string `json:"image_pull_secrets" yaml:"image_pull_secrets"`

// ImagePullPolicy overrides the pull policy applied to game server Pods
// and installation Jobs. Valid values are "Always", "IfNotPresent", and
// "Never". When empty, remote images use "Always" (so updated tags are
// re-pulled, matching the Docker backend) and ~-prefixed local images use
// "IfNotPresent". Set to "IfNotPresent" or "Never" for air-gapped clusters.
ImagePullPolicy string `default:"" json:"image_pull_policy" yaml:"image_pull_policy"`

// ServiceAccount is the name of the Kubernetes ServiceAccount assigned to
// game server Pods.
ServiceAccount string `default:"" json:"service_account" yaml:"service_account"`
Expand Down
18 changes: 9 additions & 9 deletions environment/kubernetes/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,29 @@ import (
var (
_konce sync.Once
_client kubernetes.Interface
_kerr error
)

// Client returns a shared Kubernetes clientset. The client is created once
// and reused for all subsequent calls. It uses in-cluster config when no
// kubeconfig path is specified, falling back to the provided kubeconfig file.
func Client() (kubernetes.Interface, error) {
var err error
_konce.Do(func() {
var cfg *rest.Config
kubeconfig := config.Get().Kubernetes.Kubeconfig
if kubeconfig != "" {
cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
cfg, _kerr = clientcmd.BuildConfigFromFlags("", kubeconfig)
} else {
cfg, err = rest.InClusterConfig()
cfg, _kerr = rest.InClusterConfig()
}
if err != nil {
err = errors.Wrap(err, "environment/kubernetes: failed to build config")
if _kerr != nil {
_kerr = errors.Wrap(_kerr, "environment/kubernetes: failed to build config")
return
}
_client, err = kubernetes.NewForConfig(cfg)
if err != nil {
err = errors.Wrap(err, "environment/kubernetes: failed to create clientset")
_client, _kerr = kubernetes.NewForConfig(cfg)
if _kerr != nil {
_kerr = errors.Wrap(_kerr, "environment/kubernetes: failed to create clientset")
}
})
return _client, err
return _client, _kerr
}
Loading
Loading