Skip to content

Commit c2f0afb

Browse files
authored
Harden rootfs startup for Kibana and Elasticsearch. (#5)
Set runtime sysctls/ulimits and sanitize stale UUID/keystore files so rebuilt VM images start consistently and avoid repeated crash loops.
1 parent ff49345 commit c2f0afb

2 files changed

Lines changed: 144 additions & 2 deletions

File tree

‎scripts/docker-to-rootfs.sh‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ if [ -f "$ROOTFS/usr/share/kibana/data/uuid" ]; then
176176
echo "==> Removing stale Kibana UUID file from rootfs"
177177
rm -f "$ROOTFS/usr/share/kibana/data/uuid"
178178
fi
179+
if [ -f "$ROOTFS/usr/share/elasticsearch/config/elasticsearch.keystore" ]; then
180+
echo "==> Removing Elasticsearch keystore from rootfs (recreated at boot)"
181+
rm -f "$ROOTFS/usr/share/elasticsearch/config/elasticsearch.keystore"
182+
fi
179183
180184
# Write Docker-derived runtime metadata consumed by /sbin/fc-init.
181185
echo "==> Writing /etc/firework/runtime.json"
@@ -209,7 +213,7 @@ INIT_EOF
209213
chmod 755 "$ROOTFS/sbin/init"
210214
211215
# Ensure essential directories exist in the rootfs.
212-
mkdir -p "$ROOTFS"/{proc,sys,dev,tmp,var/run}
216+
mkdir -p "$ROOTFS"/{proc,sys,dev,tmp,var}
213217
214218
echo "==> Building ext4 image (${SIZE_MB}M)"
215219
dd if=/dev/zero of="$OUTPUT" bs=1M count="$SIZE_MB" status=none

‎scripts/fc-init/main.go‎

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,16 @@ import (
2323
"syscall"
2424
)
2525

26-
const runtimeMetadataPath = "/etc/firework/runtime.json"
26+
const (
27+
runtimeMetadataPath = "/etc/firework/runtime.json"
28+
29+
vmMaxMapCountPath = "/proc/sys/vm/max_map_count"
30+
vmMaxMapCountValue = "262144"
31+
minNoFileLimit = 65535
32+
33+
kibanaUUIDPath = "/usr/share/kibana/data/uuid"
34+
elasticsearchKeystorePath = "/usr/share/elasticsearch/config/elasticsearch.keystore"
35+
)
2736

2837
type runtimeMetadata struct {
2938
User string `json:"user,omitempty"`
@@ -35,6 +44,7 @@ type runtimeMetadata struct {
3544
func main() {
3645
mountAll()
3746
setHostname()
47+
applyRuntimeTuning()
3848
meta := loadRuntimeMetadata()
3949
applyImageEnv(meta.Env)
4050
exportFireworkEnv()
@@ -128,6 +138,8 @@ func execService(meta runtimeMetadata) {
128138
argv = []string{"/sbin/init"}
129139
}
130140

141+
sanitizeRuntimeState()
142+
131143
if meta.Workdir != "" {
132144
if err := os.Chdir(meta.Workdir); err != nil {
133145
fmt.Fprintf(os.Stderr, "fc-init: chdir %s: %v\n", meta.Workdir, err)
@@ -318,3 +330,129 @@ func parseRequiredID(s string) (int, error) {
318330
}
319331
return n, nil
320332
}
333+
334+
func applyRuntimeTuning() {
335+
if err := os.WriteFile(vmMaxMapCountPath, []byte(vmMaxMapCountValue), 0o644); err != nil {
336+
fmt.Fprintf(os.Stderr, "fc-init: set vm.max_map_count=%s: %v\n", vmMaxMapCountValue, err)
337+
}
338+
if err := ensureNoFileLimit(minNoFileLimit); err != nil {
339+
fmt.Fprintf(os.Stderr, "fc-init: set nofile limit: %v\n", err)
340+
}
341+
}
342+
343+
func ensureNoFileLimit(min uint64) error {
344+
var lim syscall.Rlimit
345+
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err != nil {
346+
return fmt.Errorf("getrlimit: %w", err)
347+
}
348+
349+
newCur := lim.Cur
350+
newMax := lim.Max
351+
if newCur < min {
352+
newCur = min
353+
}
354+
if newMax < min {
355+
newMax = min
356+
}
357+
if newCur == lim.Cur && newMax == lim.Max {
358+
return nil
359+
}
360+
361+
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &syscall.Rlimit{
362+
Cur: newCur,
363+
Max: newMax,
364+
}); err != nil {
365+
return fmt.Errorf("setrlimit: %w", err)
366+
}
367+
return nil
368+
}
369+
370+
func sanitizeRuntimeState() {
371+
sanitizeKibanaUUID(kibanaUUIDPath)
372+
sanitizeElasticsearchKeystore(elasticsearchKeystorePath)
373+
}
374+
375+
func sanitizeKibanaUUID(path string) {
376+
data, ok := readSmallFile(path)
377+
if !ok {
378+
return
379+
}
380+
uuid := strings.TrimSpace(string(data))
381+
if uuid == "" || !isValidUUID(uuid) {
382+
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
383+
fmt.Fprintf(os.Stderr, "fc-init: remove invalid kibana uuid %s: %v\n", path, err)
384+
return
385+
}
386+
fmt.Fprintf(os.Stderr, "fc-init: removed invalid kibana uuid file %s\n", path)
387+
}
388+
}
389+
390+
func sanitizeElasticsearchKeystore(path string) {
391+
info, err := os.Stat(path)
392+
if err != nil {
393+
if !os.IsNotExist(err) {
394+
fmt.Fprintf(os.Stderr, "fc-init: stat %s: %v\n", path, err)
395+
}
396+
return
397+
}
398+
if !info.Mode().IsRegular() {
399+
return
400+
}
401+
// Elasticsearch expects a non-empty keystore file. Size < 16 strongly
402+
// indicates corruption (e.g. zero-byte files observed in crash loops).
403+
if info.Size() >= 16 {
404+
return
405+
}
406+
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
407+
fmt.Fprintf(os.Stderr, "fc-init: remove invalid elasticsearch keystore %s: %v\n", path, err)
408+
return
409+
}
410+
fmt.Fprintf(os.Stderr, "fc-init: removed invalid elasticsearch keystore %s\n", path)
411+
}
412+
413+
func readSmallFile(path string) ([]byte, bool) {
414+
info, err := os.Stat(path)
415+
if err != nil {
416+
if !os.IsNotExist(err) {
417+
fmt.Fprintf(os.Stderr, "fc-init: stat %s: %v\n", path, err)
418+
}
419+
return nil, false
420+
}
421+
if !info.Mode().IsRegular() {
422+
return nil, false
423+
}
424+
if info.Size() > 1024 {
425+
fmt.Fprintf(os.Stderr, "fc-init: %s too large for UUID file\n", path)
426+
return nil, false
427+
}
428+
data, err := os.ReadFile(path)
429+
if err != nil {
430+
fmt.Fprintf(os.Stderr, "fc-init: read %s: %v\n", path, err)
431+
return nil, false
432+
}
433+
return data, true
434+
}
435+
436+
func isValidUUID(v string) bool {
437+
if len(v) != 36 {
438+
return false
439+
}
440+
for i := 0; i < len(v); i++ {
441+
c := v[i]
442+
switch i {
443+
case 8, 13, 18, 23:
444+
if c != '-' {
445+
return false
446+
}
447+
default:
448+
if !isHex(c) {
449+
return false
450+
}
451+
}
452+
}
453+
return true
454+
}
455+
456+
func isHex(c byte) bool {
457+
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
458+
}

0 commit comments

Comments
 (0)