<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Peter Lee - Blog</title><link>https://blog.peterlee.app/</link><description>Recent content on Peter Lee - Blog</description><generator>Hugo</generator><language>en</language><managingEditor>peterlee0127@gmail.com (peterlee)</managingEditor><webMaster>peterlee0127@gmail.com (peterlee)</webMaster><lastBuildDate>Mon, 14 Sep 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.peterlee.app/index.xml" rel="self" type="application/rss+xml"/><item><title>Local DNS for home Kubernetes: tracing resolver loops from the router to CoreDNS</title><link>https://blog.peterlee.app/en/cloud/kubernetes-local-dns-coredns-loop/</link><pubDate>Mon, 14 Sep 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/kubernetes-local-dns-coredns-loop/</guid><description>&lt;p>Opening GitLab in a browser does not prove that Argo CD can clone its repository. Resolving a Registry name on the NUC does not prove that a Pod or the container runtime uses the same DNS path. Once browser-facing access and internal machine access are separated, a single claim that “DNS works” stops being useful.&lt;/p></description></item><item><title>Persist Vault audit logs on the host: permissions, HUP, and 90-day rotation</title><link>https://blog.peterlee.app/en/cloud/vault-audit-log-persistence-rotation/</link><pubDate>Wed, 09 Sep 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/vault-audit-log-persistence-rotation/</guid><description>&lt;p>A Vault server that delivers secrets successfully does not necessarily leave the evidence needed to explain who performed an API operation. Container server logs describe process state and errors. Audit devices record API requests and responses. Seeing output from &lt;code>docker logs vault&lt;/code> does not prove that both exist.&lt;/p></description></item><item><title>Running Qwen3.6 and Qwen3.8 in LM Studio on an M5 Pro</title><link>https://blog.peterlee.app/en/ai/lm-studio-qwen-m5-pro/</link><pubDate>Fri, 04 Sep 2026 01:05:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/ai/lm-studio-qwen-m5-pro/</guid><description>&lt;p>Installing a local large language model on an M5 Pro is straightforward. Choosing the right model format and leaving enough unified memory are the parts that need more attention. I use two 4-bit MLX models in LM Studio: Qwen3.6 35B A3B and Qwen3.8 27B. Their model files are about 20.43 GB and 16.08 GB respectively. Both support text, images, and reasoning, but their architectures and best uses differ.&lt;/p></description></item><item><title>Distribute Registry credentials across namespaces with ClusterExternalSecret</title><link>https://blog.peterlee.app/en/cloud/clusterexternalsecret-registry-credentials/</link><pubDate>Wed, 02 Sep 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/clusterexternalsecret-registry-credentials/</guid><description>&lt;p>A Deployment can look correct in a new namespace while its Pod stays in &lt;code>ImagePullBackOff&lt;/code>. The Pod references &lt;code>regcred&lt;/code>, but that Secret exists only in another namespace. Copying it once is easy. Repeating the copy for every workload and every credential rotation is the maintenance problem.&lt;/p></description></item><item><title>A Kubernetes MCP Tool That Can Look but Not Touch</title><link>https://blog.peterlee.app/en/cloud/read-only-mcp-for-kubernetes-triage/</link><pubDate>Fri, 28 Aug 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/read-only-mcp-for-kubernetes-triage/</guid><description>&lt;p>I recently built an MCP tool in &lt;code>k8s_infra/plugins/infra-observer&lt;/code> for AI-assisted infrastructure triage. It is not a wrapper around a remote terminal, and it does not hand &lt;code>kubectl&lt;/code> to a model. Its job is narrower: give the model enough information to investigate a problem without letting it change the environment.&lt;/p></description></item><item><title>Helping AI Understand My Kubernetes Infrastructure</title><link>https://blog.peterlee.app/en/cloud/ai-gitops-kubernetes-infrastructure/</link><pubDate>Fri, 28 Aug 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/ai-gitops-kubernetes-infrastructure/</guid><description>&lt;p>When I first brought AI into my Kubernetes workflow, the problem was not that it could not write YAML. The problem was that it could write YAML after looking at only one file. It could quickly produce a Deployment without knowing whether the service belonged behind Argo CD, where its Secret came from, or whether the public entry point also needed work outside the cluster.&lt;/p></description></item><item><title>Run Airflow on Kubernetes with GitOps-managed values</title><link>https://blog.peterlee.app/en/cloud/airflow-on-kubernetes-with-gitops-values/</link><pubDate>Mon, 24 Aug 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/airflow-on-kubernetes-with-gitops-values/</guid><description>&lt;p>Apache Airflow runs as a GitOps-managed platform service in my home Kubernetes
cluster. It orchestrates scheduled data work and operational workflows, while
Kubernetes provides an isolated runtime for the scheduler, API server,
Celery workers, triggerer, and their supporting services.&lt;/p></description></item><item><title>Replace Caddy with cloudflared: expose Kubernetes without opening ports 80 and 443</title><link>https://blog.peterlee.app/en/cloud/cloudflare-tunnel-kubernetes-without-open-ports/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/cloudflare-tunnel-kubernetes-without-open-ports/</guid><description>&lt;p>My &lt;code>k8s_infra&lt;/code> setup originally used Caddy as a local web reverse proxy.
External requests reached Cloudflare first, entered my home network through
forwarded HTTP or HTTPS ports, arrived at Caddy, and were finally sent to
Kubernetes.&lt;/p></description></item><item><title>Remove an accidentally committed secret from Git—locally and remotely</title><link>https://blog.peterlee.app/en/cloud/remove-committed-secret-from-git-history/</link><pubDate>Thu, 30 Jul 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/remove-committed-secret-from-git-history/</guid><description>&lt;p>This incident started with an ordinary side-project task. The project consumed
several CSV files from external sources, and some of those files contained
confidential data that should never have been versioned. While preparing the
data for the project, I accidentally added one of the sensitive CSV files to
the Git repository without noticing.&lt;/p></description></item><item><title>Building a Native Encrypted DNS App for macOS with AI Coding: DNS Security Pro</title><link>https://blog.peterlee.app/en/ios/dns-security-pro-native-macos-ai-coding/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/ios/dns-security-pro-native-macos-ai-coding/</guid><description>&lt;p>&lt;a href="https://github.com/peterlee0127/DNS-Security-Pro-macOS">DNS Security Pro for macOS&lt;/a>
lets users switch between DNS over HTTPS (DoH) and DNS over TLS (DoT) profiles
and see which profile is active. I built it with SwiftUI, the AppKit lifecycle,
and Apple&amp;rsquo;s NetworkExtension APIs as an independent macOS app, rather than
porting the iOS interface through Mac Catalyst.&lt;/p></description></item><item><title>Use Mozilla SOPS with GitOps for encrypted Kubernetes Secrets</title><link>https://blog.peterlee.app/en/cloud/gitops-sops-encrypted-secrets/</link><pubDate>Tue, 09 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/gitops-sops-encrypted-secrets/</guid><description>&lt;p>SOPS lets a GitOps repository hold encrypted Kubernetes Secret values. The
controller decrypts them during deployment:&lt;/p>
&lt;p>&lt;code>encrypted Secret in Git -&amp;gt; GitOps controller decrypts -&amp;gt; Kubernetes Secret -&amp;gt; Pod&lt;/code>&lt;/p>
&lt;p>Vault supplies runtime values from a secret manager; SOPS uses the encrypted
files in Git as the source of truth. Choosing SOPS means protecting the
decryption key alongside the infrastructure needed to rebuild the cluster.&lt;/p></description></item><item><title>Run Istio ambient mode with waypoint proxies</title><link>https://blog.peterlee.app/en/cloud/istio-ambient-waypoint-proxies/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/istio-ambient-waypoint-proxies/</guid><description>&lt;p>I use Istio ambient mode to enroll namespaces in the mesh without adding a
sidecar to every Pod. Ztunnel handles secure L4 traffic; I add a waypoint to
services that need L7 routing, authorization, or telemetry.&lt;/p></description></item><item><title>Bootstrap a new RKE cluster for GitOps</title><link>https://blog.peterlee.app/en/cloud/bootstrap-rke-cluster-for-gitops/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/bootstrap-rke-cluster-for-gitops/</guid><description>&lt;p>Argo CD can manage most of my RKE cluster after the root Application is
applied. Before that first sync, Git access, Vault authentication, and node
prerequisites need to work. Otherwise ExternalSecrets cannot read Vault,
workloads start without their Secrets, or PVCs wait for unavailable storage.&lt;/p></description></item><item><title>Build an OpenTelemetry stack for Kubernetes apps</title><link>https://blog.peterlee.app/en/cloud/opentelemetry-stack-for-kubernetes-apps/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/opentelemetry-stack-for-kubernetes-apps/</guid><description>&lt;p>My application workloads run in Kubernetes, but the observability backend does
not. OpenTelemetry Collector, Prometheus, Loki, Tempo, and Grafana run on the
NUC with Docker Compose. This keeps dashboards and historical telemetry
available even when I am rebuilding the cluster.&lt;/p></description></item><item><title>Expose Kubernetes services with Istio Gateway API</title><link>https://blog.peterlee.app/en/cloud/istio-gateway-api-ingress/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/istio-gateway-api-ingress/</guid><description>&lt;p>I use Istio Gateway API to route public hostnames to Kubernetes Services.
An external reverse proxy terminates HTTPS and forwards HTTP to a shared
Gateway in the cluster:&lt;/p>
&lt;p>&lt;code>external TLS reverse proxy -&amp;gt; Istio Gateway Service -&amp;gt; Gateway API HTTPRoute -&amp;gt; Kubernetes Service -&amp;gt; Pod&lt;/code>&lt;/p></description></item><item><title>How I use Argo CD to manage my home Kubernetes cluster</title><link>https://blog.peterlee.app/en/cloud/argocd-gitops-for-home-kubernetes/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/argocd-gitops-for-home-kubernetes/</guid><description>&lt;p>I use Argo CD to keep my home Kubernetes cluster in sync with Git.&lt;/p>
&lt;p>Before this setup, most Kubernetes resources were applied manually. That is fine
when the cluster is small, but after adding application workloads, Redis,
Longhorn, Istio, monitoring, and ingress resources, I wanted the cluster to be
rebuilt from Git as much as possible.&lt;/p></description></item><item><title>Use Vault and External Secrets in Kubernetes</title><link>https://blog.peterlee.app/en/cloud/vault-kubernetes-external-secrets/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/vault-kubernetes-external-secrets/</guid><description>&lt;p>In my home Kubernetes cluster, I use &lt;code>Vault&lt;/code> and &lt;code>External Secrets Operator&lt;/code> to
deliver secrets without storing their plaintext values in Git.&lt;/p>
&lt;p>The main idea is:&lt;/p>
&lt;p>&lt;code>Vault KV v2 -&amp;gt; ClusterSecretStore -&amp;gt; ExternalSecret -&amp;gt; Kubernetes Secret -&amp;gt; Pod&lt;/code>&lt;/p></description></item><item><title>Deploy Rancher Kubernetes Engine</title><link>https://blog.peterlee.app/en/cloud/deploy-rancher-kubernetes/</link><pubDate>Sun, 10 Oct 2021 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/deploy-rancher-kubernetes/</guid><description>&lt;p>This is a record of my 2021 RKE1 setup with Kubernetes 1.20.11. RKE1 reached
end of life on July 31, 2025; for a new cluster, follow the current
&lt;a href="https://docs.rke2.io/">RKE2 documentation&lt;/a>. The lifecycle notice is documented
by &lt;a href="https://ranchermanager.docs.rancher.com/getting-started/installation-and-upgrade/upgrade-kubernetes-without-upgrading-rancher">Rancher&lt;/a>.&lt;/p></description></item><item><title>DNS Security for iOS/macOS</title><link>https://blog.peterlee.app/en/ios/dns_security_ios/</link><pubDate>Fri, 20 Nov 2020 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/ios/dns_security_ios/</guid><description>&lt;h1 id="dns-security">DNS Security&lt;/h1>
&lt;div style="text-align:center">&lt;a href='https://apps.apple.com/us/app/id1533938029'>&lt;img src="https://blog.peterlee.app/assets/images/posts/2020-11-20-dns-security-ios/mac-logo.png" width='200px' style='alignment:center'>&lt;/a>&lt;/div>
&lt;p>DNS Security uses iOS 14 encrypted DNS profiles to protect DNS queries without
routing all network traffic through a VPN.&lt;/p>
&lt;h3 id="what-are-dns-over-https-and-dns-over-tls">What are DNS over HTTPS and DNS over TLS?&lt;/h3>
&lt;p>Even when you visit a site over HTTPS, your DNS query may still be sent over an unencrypted connection. Anyone watching the network can see which domains you request, and an attacker can potentially tamper with DNS answers to redirect visitors to phishing, malware, or surveillance sites. Your ISP, router, or network provider may also be able to track these requests. DNS over HTTPS and DNS over TLS help protect DNS queries from this kind of exposure.&lt;/p></description></item><item><title>Host your website on GitHub Pages</title><link>https://blog.peterlee.app/en/web/host-your-website-on-github-pages/</link><pubDate>Thu, 24 Oct 2019 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/web/host-your-website-on-github-pages/</guid><description>&lt;p>GitHub Pages can publish a static site at the default &lt;code>github.io&lt;/code> address or at
a domain you own. A custom domain has two parts that must agree: GitHub must
know which Pages site owns the name, and DNS must point that name to GitHub.&lt;/p></description></item><item><title>Deploy NFS StorageClass for Kubernetes</title><link>https://blog.peterlee.app/en/cloud/deploy-storage-class-for-kubernetes/</link><pubDate>Tue, 27 Aug 2019 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/deploy-storage-class-for-kubernetes/</guid><description>&lt;p>This 2019 lab used an NFS-backed &lt;code>StorageClass&lt;/code> and the old
&lt;code>nfs-client-provisioner&lt;/code>. The manifests below record that setup, including its
&lt;code>extensions/v1beta1&lt;/code> Deployment. For a new cluster, use the maintained
&lt;a href="https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner">NFS subdirectory external provisioner&lt;/a>
and its installation instructions.&lt;/p></description></item><item><title>Start a Kubernetes cluster with kubeadm</title><link>https://blog.peterlee.app/en/cloud/start_a_kubernetes_with_kubeadm/</link><pubDate>Sun, 25 Aug 2019 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/en/cloud/start_a_kubernetes_with_kubeadm/</guid><description>&lt;p>This is a record of my 2019 lab: Kubernetes 1.15.3, Docker 19.3.1, Ubuntu 18.04,
and CentOS 7. The commands below belong to that environment. For a new cluster,
use the &lt;a href="https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/">current kubeadm installation guide&lt;/a>;
the package repositories and Docker integration have changed.&lt;/p></description></item></channel></rss>