mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
* feat: add support for ulimits in compose and container creation ([#221](https://github.com/ipaddicting/uncloud/issues/221)) * refactor: move Ulimits to ContainerResources and use map, also removed GEMINI.md.
This commit is contained in:
@@ -39,3 +39,4 @@ node_modules/
|
|||||||
|
|
||||||
# VS Code
|
# VS Code
|
||||||
.vscode
|
.vscode
|
||||||
|
.devcontainer/
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
|
"github.com/docker/go-units"
|
||||||
"github.com/google/go-containerregistry/pkg/authn"
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
@@ -617,6 +618,7 @@ func (s *Server) CreateServiceContainer(
|
|||||||
Memory: spec.Container.Resources.Memory,
|
Memory: spec.Container.Resources.Memory,
|
||||||
MemoryReservation: spec.Container.Resources.MemoryReservation,
|
MemoryReservation: spec.Container.Resources.MemoryReservation,
|
||||||
DeviceRequests: spec.Container.Resources.DeviceReservations,
|
DeviceRequests: spec.Container.Resources.DeviceReservations,
|
||||||
|
Ulimits: toDockerUlimits(spec.Container.Resources.Ulimits),
|
||||||
},
|
},
|
||||||
// Restart service containers if they exit or a machine restarts unless they are explicitly stopped.
|
// Restart service containers if they exit or a machine restarts unless they are explicitly stopped.
|
||||||
// For one-off containers and batch jobs we plan to use a different service type/mode.
|
// For one-off containers and batch jobs we plan to use a different service type/mode.
|
||||||
@@ -877,6 +879,23 @@ func toDockerBindOptions(opts *api.BindOptions) *mount.BindOptions {
|
|||||||
return dockerOpts
|
return dockerOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toDockerUlimits(ulimits map[string]api.Ulimit) []*units.Ulimit {
|
||||||
|
if len(ulimits) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerUlimits := make([]*units.Ulimit, 0, len(ulimits))
|
||||||
|
for name, u := range ulimits {
|
||||||
|
dockerUlimits = append(dockerUlimits, &units.Ulimit{
|
||||||
|
Name: name,
|
||||||
|
Soft: u.Soft,
|
||||||
|
Hard: u.Hard,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return dockerUlimits
|
||||||
|
}
|
||||||
|
|
||||||
// verifyDockerVolumesExist checks if the Docker named volumes referenced in the mounts exist on the machine.
|
// verifyDockerVolumesExist checks if the Docker named volumes referenced in the mounts exist on the machine.
|
||||||
func (s *Server) verifyDockerVolumesExist(ctx context.Context, mounts []mount.Mount) error {
|
func (s *Server) verifyDockerVolumesExist(ctx context.Context, mounts []mount.Mount) error {
|
||||||
for _, m := range mounts {
|
for _, m := range mounts {
|
||||||
|
|||||||
@@ -19,4 +19,11 @@ type ContainerResources struct {
|
|||||||
MemoryReservation int64
|
MemoryReservation int64
|
||||||
// Device reservations/requests for access to things like GPUs
|
// Device reservations/requests for access to things like GPUs
|
||||||
DeviceReservations []container.DeviceRequest
|
DeviceReservations []container.DeviceRequest
|
||||||
|
// Ulimits defines the resource limits for the container.
|
||||||
|
Ulimits map[string]Ulimit
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ulimit struct {
|
||||||
|
Soft int64
|
||||||
|
Hard int64
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,6 +363,9 @@ func (s *ContainerSpec) Clone() ContainerSpec {
|
|||||||
spec.Sysctls[k] = v
|
spec.Sysctls[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if s.Resources.Ulimits != nil {
|
||||||
|
spec.Resources.Ulimits = maps.Clone(s.Resources.Ulimits)
|
||||||
|
}
|
||||||
return spec
|
return spec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ func resourcesFromCompose(service types.ServiceConfig) api.ContainerResources {
|
|||||||
CPU: int64(service.CPUS * 1e9),
|
CPU: int64(service.CPUS * 1e9),
|
||||||
Memory: int64(service.MemLimit),
|
Memory: int64(service.MemLimit),
|
||||||
MemoryReservation: int64(service.MemReservation),
|
MemoryReservation: int64(service.MemReservation),
|
||||||
|
Ulimits: ulimitsFromCompose(service.Ulimits),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert GPU device requests from compose format, appending "gpu" capability.
|
// Convert GPU device requests from compose format, appending "gpu" capability.
|
||||||
@@ -293,6 +294,33 @@ func tmpfsVolumeSpecFromCompose(serviceVolume types.ServiceVolumeConfig) api.Vol
|
|||||||
return spec
|
return spec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ulimitsFromCompose(ulimits map[string]*types.UlimitsConfig) map[string]api.Ulimit {
|
||||||
|
if len(ulimits) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make(map[string]api.Ulimit, len(ulimits))
|
||||||
|
for name, u := range ulimits {
|
||||||
|
soft := u.Soft
|
||||||
|
hard := u.Hard
|
||||||
|
if u.Single != 0 {
|
||||||
|
if soft == 0 {
|
||||||
|
soft = u.Single
|
||||||
|
}
|
||||||
|
if hard == 0 {
|
||||||
|
hard = u.Single
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res[name] = api.Ulimit{
|
||||||
|
Soft: int64(soft),
|
||||||
|
Hard: int64(hard),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// validateServicesExtensions validates extension combinations across all services in the project.
|
// validateServicesExtensions validates extension combinations across all services in the project.
|
||||||
func validateServicesExtensions(project *types.Project) error {
|
func validateServicesExtensions(project *types.Project) error {
|
||||||
for _, service := range project.Services {
|
for _, service := range project.Services {
|
||||||
|
|||||||
@@ -699,6 +699,94 @@ volumes:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServiceSpecFromCompose_Ulimits(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
composeYAML string
|
||||||
|
expected map[string]api.Ulimit
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single ulimit with soft and hard limits",
|
||||||
|
composeYAML: `
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 20000
|
||||||
|
hard: 40000
|
||||||
|
`,
|
||||||
|
expected: map[string]api.Ulimit{
|
||||||
|
"nofile": {
|
||||||
|
Soft: 20000,
|
||||||
|
Hard: 40000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single ulimit with single value (soft=hard)",
|
||||||
|
composeYAML: `
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres
|
||||||
|
ulimits:
|
||||||
|
nproc: 65535
|
||||||
|
`,
|
||||||
|
expected: map[string]api.Ulimit{
|
||||||
|
"nproc": {
|
||||||
|
Soft: 65535,
|
||||||
|
Hard: 65535,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple ulimits",
|
||||||
|
composeYAML: `
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 20000
|
||||||
|
hard: 40000
|
||||||
|
nproc: 65535
|
||||||
|
`,
|
||||||
|
expected: map[string]api.Ulimit{
|
||||||
|
"nofile": {
|
||||||
|
Soft: 20000,
|
||||||
|
Hard: 40000,
|
||||||
|
},
|
||||||
|
"nproc": {
|
||||||
|
Soft: 65535,
|
||||||
|
Hard: 65535,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty ulimits",
|
||||||
|
composeYAML: `
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres
|
||||||
|
ulimits: {}
|
||||||
|
`,
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
project, err := LoadProjectFromContent(context.Background(), tt.composeYAML)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
spec, err := ServiceSpecFromCompose(project, "db")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expected, spec.Container.Resources.Ulimits)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestServiceSpecFromCompose_XMachinesPlacement(t *testing.T) {
|
func TestServiceSpecFromCompose_XMachinesPlacement(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
Reference in New Issue
Block a user