fix: use RWO for single-replica Longhorn volumes

This commit is contained in:
2026-10-02 13:56:32 +00:00 Unverified
parent 00fe2bb2a6
commit cccecab81f
5 changed files with 36 additions and 4 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# 2.5.2-rc.1 # 2.5.2
## Fixed ## Fixed
+1 -1
View File
@@ -37,7 +37,7 @@ function cloneCommand<T extends object>(command: T): T {
export const main = defineCommand({ export const main = defineCommand({
meta: { meta: {
name: "kuber", name: "kuber",
version: "2.5.2-rc.1", version: "2.5.2",
description: "Docker Compose -> K8s translation layer", description: "Docker Compose -> K8s translation layer",
}, },
args: { args: {
+3 -1
View File
@@ -1286,7 +1286,9 @@ export function volumesToPvc(
labels: LABELS, labels: LABELS,
}, },
spec: { spec: {
accessModes: ["ReadWriteMany"], accessModes: [
policy?.replicaCount === 1 ? "ReadWriteOnce" : "ReadWriteMany",
],
storageClassName: policy storageClassName: policy
? toLonghornStorageClassName(policy) ? toLonghornStorageClassName(policy)
: undefined, : undefined,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@dmgnr/kuber", "name": "@dmgnr/kuber",
"version": "2.5.2-rc.1", "version": "2.5.2",
"description": "Docker Compose to Kubernetes translation layer", "description": "Docker Compose to Kubernetes translation layer",
"bin": { "bin": {
"kuber": "dist/index.js" "kuber": "dist/index.js"
+30
View File
@@ -38,6 +38,7 @@ describe("volume conversion", () => {
expect(claims[0]).toMatchObject({ expect(claims[0]).toMatchObject({
metadata: { name: "project-data", namespace: "project" }, metadata: { name: "project-data", namespace: "project" },
spec: { spec: {
accessModes: ["ReadWriteMany"],
resources: { requests: { storage: "1Gi" } }, resources: { requests: { storage: "1Gi" } },
storageClassName: expect.stringMatching( storageClassName: expect.stringMatching(
/^kuber-longhorn-[a-f0-9]{12}$/, /^kuber-longhorn-[a-f0-9]{12}$/,
@@ -64,6 +65,16 @@ describe("volume conversion", () => {
]); ]);
}); });
test("renders single-replica Longhorn volumes as ReadWriteOnce", () => {
const service = { volumes: ["data(20Gi on 1 fast):/data"] } as Service;
const [claim] = volumesToPvc("project", service);
const [storageClass] = volumesToStorageClasses(service);
expect(claim?.spec?.accessModes).toEqual(["ReadWriteOnce"]);
expect(claim?.spec?.storageClassName).toBe(storageClass?.metadata?.name);
expect(storageClass?.parameters?.numberOfReplicas).toBe("1");
});
test("parses inline storage sizing and placement", () => { test("parses inline storage sizing and placement", () => {
const [claim] = volumesToPvc("project", { const [claim] = volumesToPvc("project", {
volumes: ["data(20Gi on 3 fast,archive):/data"], volumes: ["data(20Gi on 3 fast,archive):/data"],
@@ -71,6 +82,7 @@ describe("volume conversion", () => {
expect(claim).toMatchObject({ expect(claim).toMatchObject({
metadata: { name: "project-data" }, metadata: { name: "project-data" },
spec: { spec: {
accessModes: ["ReadWriteMany"],
resources: { requests: { storage: "20Gi" } }, resources: { requests: { storage: "20Gi" } },
storageClassName: expect.stringMatching(/^kuber-longhorn-/), storageClassName: expect.stringMatching(/^kuber-longhorn-/),
}, },
@@ -103,11 +115,29 @@ describe("volume conversion", () => {
} as ComposeSpecification["volumes"], } as ComposeSpecification["volumes"],
); );
expect(claim?.spec).toMatchObject({ expect(claim?.spec).toMatchObject({
accessModes: ["ReadWriteMany"],
resources: { requests: { storage: "50Gi" } }, resources: { requests: { storage: "50Gi" } },
storageClassName: expect.stringMatching(/^kuber-longhorn-/), storageClassName: expect.stringMatching(/^kuber-longhorn-/),
}); });
}); });
test("uses top-level single-replica policy for the PVC and StorageClass", () => {
const service = { volumes: ["data:/data"] } as Service;
const volumes = {
data: { "x-replicaCount": 1 },
} as ComposeSpecification["volumes"];
const [claim] = volumesToPvc("project", service, process.cwd(), volumes);
const [storageClass] = volumesToStorageClasses(
service,
process.cwd(),
volumes,
);
expect(claim?.spec?.accessModes).toEqual(["ReadWriteOnce"]);
expect(claim?.spec?.storageClassName).toBe(storageClass?.metadata?.name);
expect(storageClass?.parameters?.numberOfReplicas).toBe("1");
});
test("does not create PVCs for anonymous, tmpfs, or managed claims", () => { test("does not create PVCs for anonymous, tmpfs, or managed claims", () => {
expect( expect(
volumesToPvc("project", { volumesToPvc("project", {