Späť na blog

Java Profilovanie v Hardened Kubernetes: Keď Security Blokuje Tvoj Debugger

Profilovanie v hardened clusteri je ako debugovanie cez klucovu dierku. “Potrebujem sprofilovať tento produkčný problém ale nič nefunguje.” Problém: hardened Kubernetes prostredia blokujú system cally ktoré profilery potrebujú. Tu je ako získať profilovacie dáta bez kompromitácie bezpečnosti.

Prostredie: Java 17+, Kubernetes s PodSecurityStandards, seccomp profiles, read-only root filesystem

Problém

Všetko Je Zablokované

# Pokus 1: async-profiler
java -agentpath:/profiler/libasyncProfiler.so ...
# Error: perf_event_open failed: Operation not permitted

# Pokus 2: JFR s perf events
jcmd <pid> JFR.start settings=profile
# Warning: Native profiling not available (requires elevated privileges)

# Pokus 3: Pripoj debugger
jdb -attach 5005
# Error: ptrace operation not permitted

# Pokus 4: eBPF-based profilovanie
./profile -p <pid>
# Error: bpf() syscall blocked by seccomp

# Čo blokuje všetko?

Security Vrstvy

# Vrstva 1: seccomp profil blokuje syscalls
apiVersion: v1
kind: Pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault  # Blokuje perf_event_open, ptrace, bpf

# Vrstva 2: Capabilities dropped
containers:
- name: app
  securityContext:
    capabilities:
      drop: ["ALL"]
    # CAP_SYS_PTRACE, CAP_PERFMON, CAP_BPF všetky dropped

# Vrstva 3: Read-only filesystem
    readOnlyRootFilesystem: true
    # Nemôže zapisovať profiler output alebo temp súbory

# Vrstva 4: Non-root user
    runAsNonRoot: true
    runAsUser: 1000
    # Mnoho profilerov predpokladá root prístup

Čo Stále Funguje

JFR Bez Native Profilovania

# JFR funguje aj v hardened prostrediach!
# Používa JVM-interné samplovanie, nie perf_event_open

# Spusti nahrávanie (cez jcmd alebo JMX)
jcmd <pid> JFR.start duration=60s filename=/tmp/recording.jfr

# Alebo cez JVM flags pri štarte
java -XX:StartFlightRecording=duration=60s,filename=/tmp/recording.jfr ...

# Kľúčový insight: JFR "profile" setting používa perf events
# Ale "default" setting používa pure JVM samplovanie
jcmd <pid> JFR.start settings=default filename=/tmp/recording.jfr

Získanie JFR Dát z Containera

# Problém: readOnlyRootFilesystem blokuje /tmp zápisy

# Riešenie 1: Zapisuj do emptyDir volume
volumes:
- name: profiler-output
  emptyDir: {}
volumeMounts:
- name: profiler-output
  mountPath: /profiler-data

# Potom:
jcmd <pid> JFR.start filename=/profiler-data/recording.jfr

# Skopíruj von:
kubectl cp pod-name:/profiler-data/recording.jfr ./recording.jfr

# Riešenie 2: Streamuj na stdout (šikovný hack)
jcmd <pid> JFR.dump name=1 filename=/dev/stdout | base64 > recording.b64

JVM Vstavané Samplovanie

// ThreadMXBean samplovanie - nepotrebuje špeciálne permissions
import java.lang.management.ThreadMXBean;
import java.lang.management.ManagementFactory;

public class SimpleSampler {
    public static void sample(int durationSeconds, int intervalMs) {
        ThreadMXBean tmx = ManagementFactory.getThreadMXBean();
        Map<String, Integer> stackCounts = new HashMap<>();

        long end = System.currentTimeMillis() + (durationSeconds * 1000L);
        while (System.currentTimeMillis() < end) {
            for (ThreadInfo ti : tmx.dumpAllThreads(false, false)) {
                String stack = Arrays.stream(ti.getStackTrace())
                    .limit(10)
                    .map(StackTraceElement::toString)
                    .collect(Collectors.joining("\n"));
                stackCounts.merge(stack, 1, Integer::sum);
            }
            Thread.sleep(intervalMs);
        }

        // Output ako jednoduchý flame graph formát
        stackCounts.forEach((stack, count) ->
            System.out.println(stack.replace("\n", ";") + " " + count));
    }
}

Riešenia

Možnosť 1: Sidecar Profiler s Elevated Permissions

# Pridaj privileged sidecar len pre profilovanie
# Deploynutý len keď potrebné, odstránený po

apiVersion: v1
kind: Pod
spec:
  shareProcessNamespace: true  # Sidecar vidí procesy hlavného containera

  containers:
  - name: app
    image: my-app:latest
    securityContext:
      runAsNonRoot: true
      readOnlyRootFilesystem: true

  - name: profiler
    image: async-profiler:latest
    securityContext:
      capabilities:
        add: ["SYS_PTRACE", "PERFMON"]  # Len čo treba
    command: ["sleep", "infinity"]
    volumeMounts:
    - name: profiler-output
      mountPath: /output

# Profiluj zo sidecar:
# kubectl exec -it pod-name -c profiler -- \
#   /profiler/profiler.sh -d 30 -f /output/flamegraph.html <pid>

Možnosť 2: Ephemeral Debug Container

# Kubernetes 1.23+ podporuje ephemeral containers

kubectl debug pod-name -it --image=async-profiler:latest \
  --target=app \
  --profile=sysadmin  # Pridá potrebné capabilities

# Vnútri debug containera:
/profiler/profiler.sh -d 30 -f /tmp/flamegraph.html 1

Možnosť 3: Pre-Konfigurované JFR pri Štarte

# Nakonfiguruj JFR v deployment - nepotrebné runtime attachment

containers:
- name: app
  image: my-app:latest
  env:
  - name: JAVA_TOOL_OPTIONS
    value: >-
      -XX:StartFlightRecording=
      disk=true,
      dumponexit=true,
      filename=/profiler-data/recording.jfr,
      maxsize=100m,
      settings=default

  volumeMounts:
  - name: profiler-output
    mountPath: /profiler-data

Možnosť 4: JMX Remote Access

# Zapni JMX pre remote profilovacie nástroje

containers:
- name: app
  env:
  - name: JAVA_TOOL_OPTIONS
    value: >-
      -Dcom.sun.management.jmxremote=true
      -Dcom.sun.management.jmxremote.port=9010
      -Dcom.sun.management.jmxremote.rmi.port=9010
      -Dcom.sun.management.jmxremote.authenticate=false
      -Dcom.sun.management.jmxremote.ssl=false
      -Djava.rmi.server.hostname=127.0.0.1

  ports:
  - containerPort: 9010
    name: jmx

# Port forward a pripoj sa cez VisualVM/JMC:
# kubectl port-forward pod-name 9010:9010
# jmc  # Pripoj na localhost:9010

Možnosť 5: Continuous Profiling Service

# Použi Pyroscope alebo podobné pre always-on profilovanie
# Agent používa JFR pod kapotou - nepotrebuje elevated permissions

containers:
- name: app
  env:
  - name: JAVA_TOOL_OPTIONS
    value: >-
      -javaagent:/pyroscope/pyroscope.jar
      -Dpyroscope.serverAddress=http://pyroscope.monitoring:4040
      -Dpyroscope.applicationName=my-app
      -Dpyroscope.profilingInterval=10ms

Analýza Bez Plného Profilera

Thread Dump Analýza

# Thread dumpy vždy fungujú
jcmd <pid> Thread.print > threads.txt

# Alebo cez kill signál
kill -3 <pid>  # Output na stderr

# Viacero thread dumpov + analýza
for i in {1..10}; do
  jcmd <pid> Thread.print >> threads.txt
  sleep 1
done

# Analyzuj s fastthread.io alebo podobné

Heap Dump pre Memory Problémy

# Heap dumpy fungujú bez špeciálnych permissions
jcmd <pid> GC.heap_dump /profiler-data/heap.hprof

# Analyzuj s Eclipse MAT, VisualVM, alebo jhat

GC Log Analýza

# Zapni detailné GC logovanie pri štarte
env:
- name: JAVA_TOOL_OPTIONS
  value: >-
    -Xlog:gc*=info:file=/profiler-data/gc.log:time,uptime,level,tags

# Analyzuj s GCViewer, GCEasy, alebo gceasy.io

Checklist

## Java Profilovanie v Hardened K8s

### Pred Deploymentom
- [ ] Zapni JFR pri štarte s -XX:StartFlightRecording
- [ ] Nakonfiguruj JMX remote access
- [ ] Pridaj emptyDir volume pre profiler output
- [ ] Zahrň profilovací agent do image (Pyroscope, atď.)

### Počas Incidentu
- [ ] Skús jcmd JFR.start (funguje bez privilégií)
- [ ] Zozbieraj thread dumpy (vždy funguje)
- [ ] Použi kubectl debug pre ephemeral container
- [ ] Port-forward JMX a použi remote nástroje

### Ak Potrebuješ Native Profilovanie
- [ ] Deploy profiler sidecar s SYS_PTRACE
- [ ] Použi shareProcessNamespace: true
- [ ] Odstráň sidecar po dokončení profilovania

Záver

Lekcia: hardened security je dobrá, ale plánuj observabilitu pred deployom. JFR a JMX fungujú bez elevated privileges - použi ich. Keď potrebuješ native profilovanie, použi cielenú eskaláciu privilégií (sidecar containers) namiesto spúšťania všetkého privileged.

Kľúčové nástroje fungujúce v hardened prostrediach:

  • JFR s default settings (pure JVM samplovanie)
  • Thread dumpy (vždy fungujú)
  • JMX pre remote monitoring
  • Continuous profilery ako Pyroscope ktoré používajú JFR

Súvisiace články

Súvisiace články

Citujte tento článok

Ak na článok odkazujete, pridajte pôvodnú URL a uveďte autora.

Michal Drozd. "Java Profilovanie v Hardened Kubernetes: Keď Security Blokuje Tvoj Debugger". https://www.michal-drozd.com/sk/blog/java-profiling-hardened-kubernetes/ (Publikované 7. marca 2025).