mirror of
https://github.com/pockethost/pockethost.git
synced 2025-11-24 14:35:49 +00:00
enh: dynamic port allocation
This commit is contained in:
parent
4d96492353
commit
7f63c48008
@ -41,7 +41,6 @@
|
|||||||
"express-async-errors": "^3.1.1",
|
"express-async-errors": "^3.1.1",
|
||||||
"express-sslify": "^1.2.0",
|
"express-sslify": "^1.2.0",
|
||||||
"ftp-srv": "github:pockethost/ftp-srv#0fc708bae0d5d7a55ce948767f082d6fcfb2af59",
|
"ftp-srv": "github:pockethost/ftp-srv#0fc708bae0d5d7a55ce948767f082d6fcfb2af59",
|
||||||
"get-port": "^6.1.2",
|
|
||||||
"glob": "^10.3.10",
|
"glob": "^10.3.10",
|
||||||
"gobot": "1.0.0-alpha.41",
|
"gobot": "1.0.0-alpha.41",
|
||||||
"gobot-pocketbase": "0.22.8-alpha.22",
|
"gobot-pocketbase": "0.22.8-alpha.22",
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import {
|
|||||||
mkContainerHomePath,
|
mkContainerHomePath,
|
||||||
mkDocUrl,
|
mkDocUrl,
|
||||||
mkInstanceUrl,
|
mkInstanceUrl,
|
||||||
mkInternalUrl,
|
|
||||||
mkSingleton,
|
mkSingleton,
|
||||||
now,
|
now,
|
||||||
stringify,
|
stringify,
|
||||||
@ -29,7 +28,6 @@ import {
|
|||||||
InstanceLogger,
|
InstanceLogger,
|
||||||
MothershipAdminClientService,
|
MothershipAdminClientService,
|
||||||
PocketbaseService,
|
PocketbaseService,
|
||||||
PortService,
|
|
||||||
SpawnConfig,
|
SpawnConfig,
|
||||||
proxyService,
|
proxyService,
|
||||||
} from '../../services'
|
} from '../../services'
|
||||||
@ -121,24 +119,10 @@ export const instanceService = mkSingleton(
|
|||||||
updateInstanceStatus(id, InstanceStatus.Idle)
|
updateInstanceStatus(id, InstanceStatus.Idle)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** Obtain empty port */
|
|
||||||
dbg(`Obtaining port`)
|
|
||||||
const [newPortPromise, releasePort] = PortService().alloc()
|
|
||||||
const newPort = await newPortPromise
|
|
||||||
shutdownManager.push(() => {
|
|
||||||
dbg(`shut down: releasing port`)
|
|
||||||
releasePort()
|
|
||||||
})
|
|
||||||
systemInstanceLogger.breadcrumb({ port: newPort })
|
|
||||||
dbg(`Found port`)
|
|
||||||
const internalUrl = mkInternalUrl(newPort)
|
|
||||||
dbg(`internalUrl`, internalUrl)
|
|
||||||
|
|
||||||
/** Create spawn config */
|
/** Create spawn config */
|
||||||
const spawnArgs: SpawnConfig = {
|
const spawnArgs: SpawnConfig = {
|
||||||
subdomain: instance.subdomain,
|
subdomain: instance.subdomain,
|
||||||
instanceId: instance.id,
|
instanceId: instance.id,
|
||||||
port: newPort,
|
|
||||||
dev: instance.dev,
|
dev: instance.dev,
|
||||||
extraBinds: flatten([
|
extraBinds: flatten([
|
||||||
globSync(join(INSTANCE_APP_MIGRATIONS_DIR(), '*.js')).map(
|
globSync(join(INSTANCE_APP_MIGRATIONS_DIR(), '*.js')).map(
|
||||||
@ -178,7 +162,7 @@ export const instanceService = mkSingleton(
|
|||||||
/** Spawn the child process */
|
/** Spawn the child process */
|
||||||
const childProcess = await pbService.spawn(spawnArgs)
|
const childProcess = await pbService.spawn(spawnArgs)
|
||||||
|
|
||||||
const { exitCode, stopped, started } = childProcess
|
const { exitCode, stopped, started, url: internalUrl } = childProcess
|
||||||
|
|
||||||
shutdownManager.push(() => {
|
shutdownManager.push(() => {
|
||||||
dbg(`killing ${id}`)
|
dbg(`killing ${id}`)
|
||||||
|
|||||||
@ -18,14 +18,12 @@ import {
|
|||||||
} from '../../../core'
|
} from '../../../core'
|
||||||
import { GobotService } from '../GobotService'
|
import { GobotService } from '../GobotService'
|
||||||
import { InstanceLogger } from '../InstanceLoggerService'
|
import { InstanceLogger } from '../InstanceLoggerService'
|
||||||
import { PortService } from '../PortService'
|
|
||||||
|
|
||||||
export type Env = { [_: string]: string }
|
export type Env = { [_: string]: string }
|
||||||
export type SpawnConfig = {
|
export type SpawnConfig = {
|
||||||
subdomain: string
|
subdomain: string
|
||||||
instanceId: string
|
instanceId: string
|
||||||
version?: string
|
version?: string
|
||||||
port?: number
|
|
||||||
extraBinds?: string[]
|
extraBinds?: string[]
|
||||||
env?: Env
|
env?: Env
|
||||||
stdout?: MemoryStream
|
stdout?: MemoryStream
|
||||||
@ -65,16 +63,9 @@ export const createPocketbaseService = async (
|
|||||||
const cm = createCleanupManager()
|
const cm = createCleanupManager()
|
||||||
const logger = LoggerService().create('spawn')
|
const logger = LoggerService().create('spawn')
|
||||||
const { dbg, warn, error } = logger
|
const { dbg, warn, error } = logger
|
||||||
const port =
|
|
||||||
cfg.port ||
|
|
||||||
(await (async () => {
|
|
||||||
const [defaultPort, freeDefaultPort] = await PortService().alloc()
|
|
||||||
cm.add(freeDefaultPort)
|
|
||||||
return defaultPort
|
|
||||||
})())
|
|
||||||
const _cfg: Required<SpawnConfig> = {
|
const _cfg: Required<SpawnConfig> = {
|
||||||
version: maxVersion,
|
version: maxVersion,
|
||||||
port,
|
|
||||||
extraBinds: [],
|
extraBinds: [],
|
||||||
env: {},
|
env: {},
|
||||||
stderr: new MemoryStream(),
|
stderr: new MemoryStream(),
|
||||||
@ -119,6 +110,7 @@ export const createPocketbaseService = async (
|
|||||||
const container = await new Promise<{
|
const container = await new Promise<{
|
||||||
on: EventEmitter['on']
|
on: EventEmitter['on']
|
||||||
kill: () => Promise<void>
|
kill: () => Promise<void>
|
||||||
|
portBinding: number
|
||||||
}>((resolve, reject) => {
|
}>((resolve, reject) => {
|
||||||
const docker = new Docker()
|
const docker = new Docker()
|
||||||
iLogger.info(`Starting instance`)
|
iLogger.info(`Starting instance`)
|
||||||
@ -153,7 +145,7 @@ export const createPocketbaseService = async (
|
|||||||
HostConfig: {
|
HostConfig: {
|
||||||
AutoRemove: true,
|
AutoRemove: true,
|
||||||
PortBindings: {
|
PortBindings: {
|
||||||
'8090/tcp': [{ HostPort: `${port}` }],
|
'8090/tcp': [{ HostPort: `0` }],
|
||||||
},
|
},
|
||||||
Binds,
|
Binds,
|
||||||
Ulimits: [
|
Ulimits: [
|
||||||
@ -225,17 +217,44 @@ export const createPocketbaseService = async (
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.on('start', (container: Container) => {
|
.on('start', async (container: Container) => {
|
||||||
dbg(`Got started container`, container)
|
dbg(`Got started container`, container)
|
||||||
started = true
|
started = true
|
||||||
resolve({
|
|
||||||
on: emitter.on.bind(emitter),
|
try {
|
||||||
kill: () =>
|
// Get container info to retrieve the assigned port
|
||||||
container.stop({ signal: `SIGINT` }).catch((e) => {
|
const containerInfo = await container.inspect()
|
||||||
error(e)
|
const ports = containerInfo.NetworkSettings?.Ports?.['8090/tcp']
|
||||||
return container.stop({ signal: `SIGKILL` }).catch(error)
|
|
||||||
}),
|
if (!ports || !ports[0] || !ports[0].HostPort) {
|
||||||
})
|
throw new Error('Could not get port binding from container')
|
||||||
|
}
|
||||||
|
|
||||||
|
const portBinding = parseInt(ports[0].HostPort, 10)
|
||||||
|
if (isNaN(portBinding)) {
|
||||||
|
throw new Error(`Invalid port binding: ${ports[0].HostPort}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
on: emitter.on.bind(emitter),
|
||||||
|
kill: () =>
|
||||||
|
container.stop({ signal: `SIGINT` }).catch((e) => {
|
||||||
|
error(e)
|
||||||
|
return container.stop({ signal: `SIGKILL` }).catch(error)
|
||||||
|
}),
|
||||||
|
portBinding,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
error(`Failed to get port binding: ${e}`)
|
||||||
|
reject(e)
|
||||||
|
try {
|
||||||
|
await container.stop()
|
||||||
|
} catch (stopError) {
|
||||||
|
error(
|
||||||
|
`Failed to stop container after port binding error: ${stopError}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
error(`Error starting container: ${e}`)
|
error(`Error starting container: ${e}`)
|
||||||
@ -255,7 +274,8 @@ export const createPocketbaseService = async (
|
|||||||
dbg(`Instance exited with ${code}`)
|
dbg(`Instance exited with ${code}`)
|
||||||
cm.shutdown().catch(error)
|
cm.shutdown().catch(error)
|
||||||
})
|
})
|
||||||
const url = mkInternalUrl(port)
|
|
||||||
|
const url = mkInternalUrl(container.portBinding)
|
||||||
logger.breadcrumb({ url })
|
logger.breadcrumb({ url })
|
||||||
dbg(`Making exit hook for ${url}`)
|
dbg(`Making exit hook for ${url}`)
|
||||||
const unsub = asyncExitHook(async () => {
|
const unsub = asyncExitHook(async () => {
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
import getPort from 'get-port'
|
|
||||||
import { INITIAL_PORT_POOL_SIZE } from '../../core'
|
|
||||||
import { Allocator, ResourceAllocator, ioc } from '../common'
|
|
||||||
|
|
||||||
export type Config = { maxPorts: number }
|
|
||||||
|
|
||||||
export const PortService = () => {
|
|
||||||
try {
|
|
||||||
return ioc<Allocator<number>>('portAllocator')
|
|
||||||
} catch (e) {
|
|
||||||
return ioc(
|
|
||||||
'portAllocator',
|
|
||||||
ResourceAllocator(INITIAL_PORT_POOL_SIZE(), getPort),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,6 +2,5 @@ export * from './InstanceLoggerService'
|
|||||||
export * from './InstanceService'
|
export * from './InstanceService'
|
||||||
export * from './MothershipAdminClientService'
|
export * from './MothershipAdminClientService'
|
||||||
export * from './PocketBaseService'
|
export * from './PocketBaseService'
|
||||||
export * from './PortService'
|
|
||||||
export * from './ProxyService'
|
export * from './ProxyService'
|
||||||
export * from './RealtimeLog'
|
export * from './RealtimeLog'
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@ -370,9 +370,6 @@ importers:
|
|||||||
ftp-srv:
|
ftp-srv:
|
||||||
specifier: github:pockethost/ftp-srv#0fc708bae0d5d7a55ce948767f082d6fcfb2af59
|
specifier: github:pockethost/ftp-srv#0fc708bae0d5d7a55ce948767f082d6fcfb2af59
|
||||||
version: https://codeload.github.com/pockethost/ftp-srv/tar.gz/0fc708bae0d5d7a55ce948767f082d6fcfb2af59
|
version: https://codeload.github.com/pockethost/ftp-srv/tar.gz/0fc708bae0d5d7a55ce948767f082d6fcfb2af59
|
||||||
get-port:
|
|
||||||
specifier: ^6.1.2
|
|
||||||
version: 6.1.2
|
|
||||||
glob:
|
glob:
|
||||||
specifier: ^10.3.10
|
specifier: ^10.3.10
|
||||||
version: 10.4.5
|
version: 10.4.5
|
||||||
@ -2968,10 +2965,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==}
|
resolution: {integrity: sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
get-port@6.1.2:
|
|
||||||
resolution: {integrity: sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==}
|
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
|
||||||
|
|
||||||
get-stream@2.3.1:
|
get-stream@2.3.1:
|
||||||
resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==}
|
resolution: {integrity: sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -7778,8 +7771,6 @@ snapshots:
|
|||||||
|
|
||||||
get-port@3.2.0: {}
|
get-port@3.2.0: {}
|
||||||
|
|
||||||
get-port@6.1.2: {}
|
|
||||||
|
|
||||||
get-stream@2.3.1:
|
get-stream@2.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user