Skip to content

Commit 05d8f99

Browse files
alanopsclaude
andcommitted
Fix Kubernetes scenario issues
- Mount Docker socket for k8s scenarios to enable kind cluster creation - Move setup.sh execution from build-time to runtime via entrypoint script - Add automatic Docker network creation on server startup - Add Docker image validation before container creation - Improve error handling and logging throughout the server - Fix missing dependencies in base Dockerfile (lsb-release, unzip) Resolves GitHub issues #6, #7, #8, #9, #10 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2277005 commit 05d8f99

5 files changed

Lines changed: 162 additions & 36 deletions

File tree

docker/Dockerfile.scenario-base

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
11
FROM ubuntu:22.04
22

3+
RUN apt-get update && apt-get install -y curl gnupg
4+
RUN curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
5+
RUN echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
6+
37
# Install base tools
4-
RUN apt-get update && apt-get install -y \
5-
curl \
6-
wget \
7-
git \
8-
vim \
9-
nano \
10-
htop \
11-
net-tools \
12-
dnsutils \
13-
traceroute \
14-
telnet \
15-
jq \
16-
python3 \
17-
python3-pip \
18-
nodejs \
19-
npm \
20-
docker.io \
21-
kubectl \
22-
&& rm -rf /var/lib/apt/lists/*
8+
RUN apt-get update && apt-get install -y curl wget git vim nano htop net-tools dnsutils traceroute telnet jq python3 python3-pip nodejs npm docker.io kubectl lsb-release unzip && rm -rf /var/lib/apt/lists/*
239

2410
# Install Terraform
2511
RUN wget -O- https://apt.releases.hashicorp.com/gpg | apt-key add - \

next-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
4+
// NOTE: This file should not be edited
5+
// see https://nextjs.org/docs/basic-features/typescript for more information.

scenarios/kubernetes/keycloak-crashloop/Dockerfile

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ COPY --chown=devops:devops setup.sh /home/devops/
1212
COPY --chown=devops:devops manifests/ /home/devops/manifests/
1313
COPY --chown=devops:devops solution/ /home/devops/.solution/
1414

15-
USER devops
15+
USER root
16+
17+
# Copy entrypoint script
18+
COPY --chown=devops:devops entrypoint.sh /home/devops/
1619

1720
# Make scripts executable
18-
RUN chmod +x /home/devops/setup.sh
21+
RUN chmod +x /home/devops/setup.sh /home/devops/entrypoint.sh
1922

20-
# Initialize the broken scenario
21-
RUN /home/devops/setup.sh
23+
USER devops
2224

23-
CMD ["/bin/bash"]
25+
# Use entrypoint to run setup at container start time
26+
ENTRYPOINT ["/home/devops/entrypoint.sh"]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
# Run the setup script to initialize the scenario
4+
/home/devops/setup.sh
5+
6+
# Keep the container running with bash
7+
exec /bin/bash

src/server/index.ts

Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,20 @@ io.on('connection', (socket) => {
3535

3636
const scenarioId = socket.handshake.query.scenarioId as string
3737

38+
if (!scenarioId) {
39+
socket.emit('output', '\\r\\nError: No scenario ID provided\\r\\n')
40+
socket.disconnect()
41+
return
42+
}
43+
44+
console.log(`Starting scenario '${scenarioId}' for socket ${socket.id}`)
45+
3846
// Start a new container for this scenario
39-
startScenarioContainer(socket, scenarioId)
47+
startScenarioContainer(socket, scenarioId).catch((error) => {
48+
console.error(`Failed to start scenario ${scenarioId}:`, error)
49+
socket.emit('output', `\\r\\nError: Failed to start scenario: ${error.message}\\r\\n`)
50+
socket.disconnect()
51+
})
4052

4153
socket.on('input', (data: string) => {
4254
const session = sessions.get(socket.id)
@@ -58,7 +70,16 @@ io.on('connection', (socket) => {
5870
})
5971
})
6072

61-
function startScenarioContainer(socket: any, scenarioId: string) {
73+
async function checkDockerImage(imageName: string): Promise<boolean> {
74+
return new Promise((resolve) => {
75+
const checkImage = spawn('docker', ['image', 'inspect', imageName])
76+
checkImage.on('exit', (code) => {
77+
resolve(code === 0)
78+
})
79+
})
80+
}
81+
82+
async function startScenarioContainer(socket: any, scenarioId: string) {
6283
// Map scenario IDs to Docker images
6384
const scenarioImages: Record<string, string> = {
6485
'k8s-crashloop': 'devopslearn/scenario-keycloak-crashloop',
@@ -69,15 +90,35 @@ function startScenarioContainer(socket: any, scenarioId: string) {
6990

7091
const imageName = scenarioImages[scenarioId] || 'devopslearn/scenario-base'
7192

72-
// Start Docker container with TTY
73-
const dockerProcess = spawn('docker', [
93+
// Check if Docker image exists
94+
const imageExists = await checkDockerImage(imageName)
95+
if (!imageExists) {
96+
socket.emit('output', `\r\nError: Docker image '${imageName}' not found.\r\n`)
97+
socket.emit('output', `Please run 'make scenario-build' to build the scenario images.\r\n`)
98+
socket.emit('output', `\r\nDisconnecting...\r\n`)
99+
socket.disconnect()
100+
return
101+
}
102+
103+
// Build Docker run arguments
104+
const args = [
74105
'run',
75106
'-it',
76107
'--rm',
77108
'--name', `devops-dojo-${socket.id}`,
78109
'--network', 'devops-dojo-net',
79-
imageName
80-
], {
110+
]
111+
112+
// Mount Docker socket for Kubernetes scenarios that need it
113+
const k8sScenarios = ['k8s-crashloop', 'k8s-dns', 'k8s-istio']
114+
if (k8sScenarios.includes(scenarioId)) {
115+
args.push('-v', '/var/run/docker.sock:/var/run/docker.sock')
116+
}
117+
118+
args.push(imageName)
119+
120+
// Start Docker container with TTY
121+
const dockerProcess = spawn('docker', args, {
81122
env: { ...process.env, TERM: 'xterm-256color' }
82123
})
83124

@@ -94,31 +135,115 @@ function startScenarioContainer(socket: any, scenarioId: string) {
94135
})
95136

96137
dockerProcess.stderr.on('data', (data) => {
97-
socket.emit('output', data.toString())
138+
const errorMessage = data.toString()
139+
console.error(`Container ${scenarioId} stderr:`, errorMessage)
140+
socket.emit('output', errorMessage)
141+
})
142+
143+
dockerProcess.on('error', (error) => {
144+
console.error(`Failed to start container for scenario ${scenarioId}:`, error)
145+
socket.emit('output', `\\r\\nError: Failed to start container: ${error.message}\\r\\n`)
146+
sessions.delete(socket.id)
98147
})
99148

100149
dockerProcess.on('exit', (code) => {
150+
console.log(`Container for scenario ${scenarioId} exited with code ${code}`)
101151
socket.emit('output', `\\r\\nContainer exited with code ${code}\\r\\n`)
102152
sessions.delete(socket.id)
103153
})
104154

105-
// Notify client that scenario is ready
155+
// Notify client that scenario is ready after a short delay
156+
// TODO: Replace with actual readiness check
106157
setTimeout(() => {
107-
socket.emit('scenario-ready')
158+
// Check if container is still running
159+
if (sessions.has(socket.id)) {
160+
socket.emit('scenario-ready')
161+
console.log(`Scenario ${scenarioId} is ready for socket ${socket.id}`)
162+
}
108163
}, 2000)
109164
}
110165

111166
function cleanupSession(socketId: string) {
112167
const session = sessions.get(socketId)
113168
if (session) {
169+
console.log(`Cleaning up session for ${socketId}, scenario: ${session.scenarioId}`)
114170
// Kill the Docker container
115-
spawn('docker', ['kill', session.containerId])
171+
const killProcess = spawn('docker', ['kill', session.containerId])
172+
killProcess.on('error', (error) => {
173+
console.error(`Error killing container ${session.containerId}:`, error)
174+
})
116175
sessions.delete(socketId)
117176
}
118177
}
119178

179+
// Check if Docker daemon is accessible
180+
async function checkDockerDaemon(): Promise<boolean> {
181+
return new Promise((resolve) => {
182+
const checkDocker = spawn('docker', ['version'])
183+
checkDocker.on('exit', (code) => {
184+
resolve(code === 0)
185+
})
186+
checkDocker.on('error', () => {
187+
resolve(false)
188+
})
189+
})
190+
}
191+
192+
// Ensure Docker network exists
193+
async function ensureDockerNetwork() {
194+
try {
195+
// Check if network exists
196+
const checkNetwork = spawn('docker', ['network', 'inspect', 'devops-dojo-net'])
197+
198+
await new Promise((resolve) => {
199+
checkNetwork.on('exit', (code) => {
200+
if (code !== 0) {
201+
// Network doesn't exist, create it
202+
console.log('Creating Docker network: devops-dojo-net')
203+
const createNetwork = spawn('docker', ['network', 'create', 'devops-dojo-net'])
204+
createNetwork.on('exit', (createCode) => {
205+
if (createCode === 0) {
206+
console.log('Docker network created successfully')
207+
} else {
208+
console.error('Failed to create Docker network')
209+
}
210+
resolve(null)
211+
})
212+
} else {
213+
console.log('Docker network already exists')
214+
resolve(null)
215+
}
216+
})
217+
})
218+
} catch (error) {
219+
console.error('Error checking/creating Docker network:', error)
220+
}
221+
}
222+
120223
const PORT = process.env.PORT || 3001
121224

122-
server.listen(PORT, () => {
123-
console.log(`DevOps Dojo server running on port ${PORT}`)
225+
// Initialize server with Docker checks
226+
async function initializeServer() {
227+
console.log('Checking Docker daemon...')
228+
const dockerAvailable = await checkDockerDaemon()
229+
230+
if (!dockerAvailable) {
231+
console.error('Error: Docker daemon is not accessible')
232+
console.error('Please ensure Docker is installed and running')
233+
process.exit(1)
234+
}
235+
236+
console.log('Docker daemon is accessible')
237+
238+
await ensureDockerNetwork()
239+
240+
server.listen(PORT, () => {
241+
console.log(`DevOps Dojo server running on port ${PORT}`)
242+
})
243+
}
244+
245+
// Start the server
246+
initializeServer().catch((error) => {
247+
console.error('Failed to initialize server:', error)
248+
process.exit(1)
124249
})

0 commit comments

Comments
 (0)