SiamCafe.net Blog
Technology

Immutable OS Fedora CoreOS Community Building — ระบบปฏิบัติการแบบ Immutable และสร้าง Community

immutable os fedora coreos community building
Immutable OS Fedora CoreOS Community Building | SiamCafe Blog
2026-05-24· อ. บอม — SiamCafe.net· 911 คำ

Immutable OS และ Fedora CoreOS คืออะไร

Immutable OS คือระบบปฏิบัติการที่ root filesystem เป็น read-only ไม่สามารถแก้ไขได้ขณะรัน การเปลี่ยนแปลงทำผ่าน atomic updates ที่สร้าง filesystem snapshot ใหม่ทั้งหมด ถ้า update มีปัญหาสามารถ rollback ไป version ก่อนหน้าได้ทันที

Fedora CoreOS (FCOS) เป็น immutable Linux distribution ที่ออกแบบมาสำหรับรัน containerized workloads เป็นการรวม Fedora Atomic Host กับ Container Linux (CoreOS) เข้าด้วยกัน ให้ minimal OS ที่มี automatic updates, container runtime (Podman, Docker) และ provisioning ผ่าน Ignition

ข้อดีของ Immutable OS ได้แก่ Security ที่ดีขึ้นเพราะ filesystem ถูก lock ลด attack surface, Reliability จาก atomic updates ที่ rollback ได้, Consistency ทุกเครื่องเหมือนกัน 100% ไม่มี configuration drift, Reproducibility สร้าง environment เดิมได้ทุกครั้ง และ Simplicity ลด management overhead

Immutable OS อื่นๆ ได้แก่ Flatcar Container Linux, Bottlerocket (AWS), Talos Linux (Kubernetes), NixOS (functional package management) และ openSUSE MicroOS แต่ละตัวมี target use case ต่างกัน Fedora CoreOS เหมาะสำหรับ general-purpose container hosting

ติดตั้ง Fedora CoreOS ด้วย Ignition

Provision Fedora CoreOS ด้วย Ignition configuration

# === Fedora CoreOS Installation ===

# 1. ติดตั้ง Butane (config transpiler)
# Butane แปลง YAML config เป็น Ignition JSON

# Linux
curl -LO https://github.com/coreos/butane/releases/latest/download/butane-x86_64-unknown-linux-gnu
chmod +x butane-x86_64-unknown-linux-gnu
sudo mv butane-x86_64-unknown-linux-gnu /usr/local/bin/butane

# macOS
brew install butane

# ตรวจสอบ
butane --version

# 2. สร้าง Butane Config
cat > config.bu << 'YAML'
variant: fcos
version: "1.5.0"
passwd:
  users:
    - name: core
      ssh_authorized_keys:
        - ssh-rsa AAAA...your-public-key...
      groups:
        - sudo
        - docker
    - name: admin
      ssh_authorized_keys:
        - ssh-rsa AAAA...admin-key...
      groups:
        - sudo

storage:
  files:
    - path: /etc/hostname
      mode: 0644
      contents:
        inline: fcos-node-1
    
    - path: /etc/sysctl.d/90-custom.conf
      mode: 0644
      contents:
        inline: |
          net.ipv4.ip_forward = 1
          net.core.somaxconn = 65535
          vm.max_map_count = 262144
    
    - path: /etc/zincati/config.d/55-updates-strategy.toml
      mode: 0644
      contents:
        inline: |
          [updates]
          strategy = "periodic"
          [[updates.periodic.window]]
          days = [ "Sun" ]
          start_time = "03:00"
          length_minutes = 120
    
    - path: /opt/scripts/health-check.sh
      mode: 0755
      contents:
        inline: |
          #!/bin/bash
          echo "=== System Health Check ==="
          echo "Hostname: $(hostname)"
          echo "Uptime: $(uptime -p)"
          echo "OS: $(rpm-ostree status --json | jq -r '.deployments[0].version')"
          echo "Containers: $(podman ps -q | wc -l) running"
          echo "Disk: $(df -h / | tail -1 | awk '{print $5}') used"
          echo "Memory: $(free -h | grep Mem | awk '{print $3"/"$2}')"

  directories:
    - path: /opt/containers
      mode: 0755
    - path: /opt/data
      mode: 0755

systemd:
  units:
    - name: podman-auto-update.timer
      enabled: true
    
    - name: health-check.timer
      enabled: true
      contents: |
        [Unit]
        Description=Periodic health check
        [Timer]
        OnCalendar=*:0/15
        [Install]
        WantedBy=timers.target
    
    - name: health-check.service
      contents: |
        [Unit]
        Description=System health check
        [Service]
        Type=oneshot
        ExecStart=/opt/scripts/health-check.sh
YAML

# 3. Transpile to Ignition
butane --pretty --strict config.bu > config.ign

# 4. ติดตั้ง (bare metal)
# Download Fedora CoreOS ISO
curl -LO https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/latest/x86_64/fedora-coreos-live.x86_64.iso

# Install to disk
sudo coreos-installer install /dev/sda \
    --ignition-file config.ign \
    --stream stable

# 5. ติดตั้ง (QEMU/KVM)
IGNITION_CONFIG="$(pwd)/config.ign"
IMAGE="fedora-coreos-qemu.x86_64.qcow2"

qemu-img create -f qcow2 -b "$IMAGE" -F qcow2 fcos-node.qcow2 20G

virt-install \
    --name fcos-node-1 \
    --ram 4096 --vcpus 2 \
    --import \
    --disk fcos-node.qcow2 \
    --os-variant fedora-coreos-stable \
    --network network=default \
    --qemu-commandline="-fw_cfg name=opt/com.coreos/config, file=$IGNITION_CONFIG" \
    --noautoconsole

echo "Fedora CoreOS installed"

จัดการ Container Workloads บน CoreOS

รัน containers ด้วย Podman Quadlet

# === Podman Quadlet (systemd-native containers) ===
# Quadlet = declarative container management ผ่าน systemd

# สร้าง container unit files
# /etc/containers/systemd/nginx.container
cat > /etc/containers/systemd/nginx.container << 'UNIT'
[Unit]
Description=Nginx Web Server
After=network-online.target

[Container]
Image=docker.io/library/nginx:alpine
PublishPort=80:80
PublishPort=443:443
Volume=/opt/data/nginx/html:/usr/share/nginx/html:ro,Z
Volume=/opt/data/nginx/conf:/etc/nginx/conf.d:ro,Z
AutoUpdate=registry
HealthCmd=curl -f http://localhost/ || exit 1
HealthInterval=30s

[Service]
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
UNIT

# สร้าง PostgreSQL container
cat > /etc/containers/systemd/postgres.container << 'UNIT'
[Unit]
Description=PostgreSQL Database
After=network-online.target

[Container]
Image=docker.io/library/postgres:16-alpine
PublishPort=5432:5432
Volume=postgres-data.volume:/var/lib/postgresql/data:Z
Environment=POSTGRES_PASSWORD=secure_password
Environment=POSTGRES_DB=appdb
Environment=POSTGRES_USER=appuser
HealthCmd=pg_isready -U appuser
HealthInterval=30s

[Service]
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
UNIT

# สร้าง Volume
cat > /etc/containers/systemd/postgres-data.volume << 'UNIT'
[Volume]
Driver=local
UNIT

# สร้าง Network
cat > /etc/containers/systemd/app.network << 'UNIT'
[Network]
Subnet=172.20.0.0/16
Gateway=172.20.0.1
UNIT

# Reload และ start
systemctl daemon-reload
systemctl start nginx
systemctl start postgres

# ตรวจสอบ status
systemctl status nginx
systemctl status postgres
podman ps

# === Podman Auto-Update ===
# ตรวจสอบ updates
podman auto-update --dry-run

# Apply updates
podman auto-update

# === Podman Pod (group containers) ===
# สร้าง pod สำหรับ application stack
podman pod create --name app-stack \
    -p 8080:8080 \
    -p 5432:5432

# Add containers to pod
podman run -d --pod app-stack \
    --name app-web \
    -e DATABASE_URL=postgresql://appuser:password@localhost:5432/appdb \
    my-app:latest

podman run -d --pod app-stack \
    --name app-db \
    -e POSTGRES_PASSWORD=password \
    -v pgdata:/var/lib/postgresql/data \
    postgres:16-alpine

# Generate systemd units from pod
podman generate systemd --new --files --name app-stack
sudo mv *.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now pod-app-stack

echo "Container workloads configured"

Automatic Updates และ rpm-ostree

จัดการ updates และ package layering

#!/usr/bin/env python3
# fcos_manager.py — Fedora CoreOS Management Tool
import subprocess
import json
import logging
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("fcos_manager")

class FCOSManager:
    def __init__(self):
        self.deployments = []
    
    def get_status(self):
        result = subprocess.run(
            ["rpm-ostree", "status", "--json"],
            capture_output=True, text=True,
        )
        data = json.loads(result.stdout)
        
        self.deployments = []
        for dep in data.get("deployments", []):
            self.deployments.append({
                "version": dep.get("version", "unknown"),
                "timestamp": dep.get("timestamp", 0),
                "booted": dep.get("booted", False),
                "staged": dep.get("staged", False),
                "checksum": dep.get("checksum", "")[:12],
                "packages_layered": dep.get("requested-packages", []),
                "pinned": dep.get("pinned", False),
            })
        
        return self.deployments
    
    def check_updates(self):
        result = subprocess.run(
            ["rpm-ostree", "upgrade", "--check"],
            capture_output=True, text=True,
        )
        
        if "AvailableUpdate" in result.stdout:
            logger.info("Update available")
            return True
        else:
            logger.info("System is up to date")
            return False
    
    def stage_update(self):
        logger.info("Staging update...")
        result = subprocess.run(
            ["rpm-ostree", "upgrade"],
            capture_output=True, text=True,
        )
        
        if result.returncode == 0:
            logger.info("Update staged. Reboot to apply.")
            return True
        else:
            logger.error(f"Update failed: {result.stderr}")
            return False
    
    def rollback(self):
        logger.info("Rolling back to previous deployment...")
        result = subprocess.run(
            ["rpm-ostree", "rollback"],
            capture_output=True, text=True,
        )
        
        if result.returncode == 0:
            logger.info("Rollback staged. Reboot to apply.")
            return True
        return False
    
    def install_package(self, packages):
        if isinstance(packages, str):
            packages = [packages]
        
        logger.info(f"Layering packages: {packages}")
        cmd = ["rpm-ostree", "install", "--apply-live"] + packages
        result = subprocess.run(cmd, capture_output=True, text=True)
        
        if result.returncode == 0:
            logger.info("Packages layered successfully")
            return True
        else:
            logger.error(f"Failed: {result.stderr}")
            return False
    
    def remove_package(self, packages):
        if isinstance(packages, str):
            packages = [packages]
        
        cmd = ["rpm-ostree", "uninstall"] + packages
        result = subprocess.run(cmd, capture_output=True, text=True)
        return result.returncode == 0
    
    def pin_deployment(self, index=0):
        subprocess.run(
            ["ostree", "admin", "pin", str(index)],
            capture_output=True, text=True,
        )
        logger.info(f"Pinned deployment {index}")
    
    def get_zincati_status(self):
        result = subprocess.run(
            ["systemctl", "status", "zincati", "--no-pager"],
            capture_output=True, text=True,
        )
        
        return {
            "active": "active (running)" in result.stdout,
            "output": result.stdout[:500],
        }
    
    def generate_report(self):
        deployments = self.get_status()
        zincati = self.get_zincati_status()
        
        report = {
            "timestamp": datetime.utcnow().isoformat(),
            "hostname": subprocess.getoutput("hostname"),
            "current_version": next(
                (d["version"] for d in deployments if d["booted"]), "unknown"
            ),
            "deployments": deployments,
            "auto_updates": zincati["active"],
            "containers_running": int(
                subprocess.getoutput("podman ps -q | wc -l")
            ),
        }
        
        return report

# manager = FCOSManager()
# status = manager.get_status()
# for dep in status:
#     marker = " (booted)" if dep["booted"] else ""
#     print(f"  {dep['version']}{marker} [{dep['checksum']}]")
# 
# if manager.check_updates():
#     manager.stage_update()
#
# report = manager.generate_report()
# print(json.dumps(report, indent=2))

สร้าง CoreOS Cluster ด้วย Butane Config

Provision multi-node cluster

#!/bin/bash
# provision_cluster.sh — Provision Fedora CoreOS Cluster
set -euo pipefail

NODES=("node1:10.0.0.11" "node2:10.0.0.12" "node3:10.0.0.13")
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
CLUSTER_TOKEN="$(openssl rand -hex 16)"

generate_butane() {
    local hostname=$1
    local ip=$2
    local role=$3
    
    cat << EOF
variant: fcos
version: "1.5.0"
passwd:
  users:
    - name: core
      ssh_authorized_keys:
        - 
      groups: [sudo, docker]

storage:
  files:
    - path: /etc/hostname
      mode: 0644
      contents:
        inline: 
    
    - path: /etc/NetworkManager/system-connections/static.nmconnection
      mode: 0600
      contents:
        inline: |
          [connection]
          id=static
          type=ethernet
          [ipv4]
          method=manual
          addresses=/24
          gateway=10.0.0.1
          dns=8.8.8.8;8.8.4.4
    
    - path: /etc/sysctl.d/99-kubernetes.conf
      mode: 0644
      contents:
        inline: |
          net.bridge.bridge-nf-call-iptables = 1
          net.bridge.bridge-nf-call-ip6tables = 1
          net.ipv4.ip_forward = 1
          vm.max_map_count = 262144
    
    - path: /etc/modules-load.d/kubernetes.conf
      mode: 0644
      contents:
        inline: |
          overlay
          br_netfilter
    
    - path: /opt/cluster/role
      mode: 0644
      contents:
        inline: 
    
    - path: /opt/cluster/token
      mode: 0600
      contents:
        inline: 
    
    - path: /opt/scripts/setup-node.sh
      mode: 0755
      contents:
        inline: |
          #!/bin/bash
          echo "Setting up \$(hostname) as \$(cat /opt/cluster/role)"
          
          # Install k3s
          if [ "\$(cat /opt/cluster/role)" = "controller" ]; then
            curl -sfL https://get.k3s.io | sh -s - server \\
              --token \$(cat /opt/cluster/token) \\
              --tls-san 
          else
            curl -sfL https://get.k3s.io | sh -s - agent \\
              --server https://10.0.0.11:6443 \\
              --token \$(cat /opt/cluster/token)
          fi

    - path: /etc/zincati/config.d/55-updates.toml
      mode: 0644
      contents:
        inline: |
          [updates]
          strategy = "periodic"
          [[updates.periodic.window]]
          days = ["Sun"]
          start_time = "04:00"
          length_minutes = 60

systemd:
  units:
    - name: setup-node.service
      enabled: true
      contents: |
        [Unit]
        Description=Initial node setup
        After=network-online.target
        ConditionPathExists=!/opt/cluster/.setup-done
        [Service]
        Type=oneshot
        ExecStart=/opt/scripts/setup-node.sh
        ExecStartPost=/usr/bin/touch /opt/cluster/.setup-done
        [Install]
        WantedBy=multi-user.target
EOF
}

# Generate configs for each node
echo "=== Generating cluster configs ==="

for node_entry in ""; do
    hostname=""
    ip=""
    
    if [ "$hostname" = "node1" ]; then
        role="controller"
    else
        role="worker"
    fi
    
    echo "Generating config for  () as "
    generate_butane "$hostname" "$ip" "$role" > ".bu"
    butane --pretty --strict ".bu" > ".ign"
    echo "  Created .ign"
done

echo ""
echo "=== Cluster Configuration ==="
echo "Token: "
echo "Controller: node1 (10.0.0.11)"
echo "Workers: node2 (10.0.0.12), node3 (10.0.0.13)"
echo ""
echo "Deploy each node with its .ign file"
echo "After boot, k3s will auto-configure the cluster"

Community Building สำหรับ Open Source Projects

แนวทางสร้าง community สำหรับ open source

# === Open Source Community Building Strategies ===

# 1. Documentation First
# ===================================
# README.md ที่ดีต้องมี:
# - Clear project description (1-2 sentences)
# - Quick start (< 5 minutes to first success)
# - Installation instructions (all platforms)
# - Usage examples with code
# - Contributing guidelines
# - License information
# - Badges (CI status, version, downloads)

# CONTRIBUTING.md template:
# - How to report bugs (issue template)
# - How to suggest features
# - Development setup instructions
# - Code style guidelines
# - Pull request process
# - Code of conduct reference

# 2. Issue Management
# ===================================
# Label system:
# - good-first-issue: สำหรับ newcomers
# - help-wanted: ต้องการความช่วยเหลือ
# - bug: confirmed bugs
# - enhancement: feature requests
# - documentation: docs improvements
# - priority-high/medium/low: prioritization

# Issue templates (.github/ISSUE_TEMPLATE/):
# bug_report.md:
# ---
# name: Bug Report
# about: Report a bug
# labels: bug
# ---
# **Describe the bug**
# **Steps to reproduce**
# **Expected behavior**
# **Environment** (OS, version, etc.)
# **Screenshots/Logs**

# 3. Contributor Experience
# ===================================
# Make first contribution easy:
# - Pre-configured dev container (.devcontainer/)
# - Makefile with common commands
# - CI that runs on PRs automatically
# - Automated code formatting (pre-commit hooks)
# - Clear PR template

# .github/pull_request_template.md:
# ## Description
# ## Type of change
# - [ ] Bug fix
# - [ ] New feature
# - [ ] Breaking change
# - [ ] Documentation update
# ## Testing
# - [ ] Unit tests pass
# - [ ] Manual testing done
# ## Checklist
# - [ ] Code follows project style
# - [ ] Self-review completed
# - [ ] Documentation updated

# 4. Communication Channels
# ===================================
# - GitHub Discussions: Q&A, ideas, show-and-tell
# - Discord/Matrix: real-time chat
# - Blog/Newsletter: project updates
# - Social media: announcements, community highlights
# - Monthly community calls: roadmap, demos

# 5. Recognition
# ===================================
# - All Contributors bot (recognize non-code contributions)
# - Contributor spotlight in release notes
# - Swag for significant contributors
# - Maintainer pathway for active contributors

# 6. Governance
# ===================================
# - Clear decision-making process
# - RFC process for major changes
# - Maintainer guidelines
# - Transparent roadmap
# - Regular releases with changelogs

# 7. Metrics to Track
# ===================================
# - Stars/Forks growth rate
# - Issue response time (target: < 48h)
# - PR review time (target: < 1 week)
# - Contributor count (new per month)
# - Bus factor (knowledge distribution)
# - Time to first contribution

echo "Community building strategies documented"

FAQ คำถามที่พบบ่อย

Q: Immutable OS ติดตั้ง packages เพิ่มได้ไหม?

A: ได้ด้วย rpm-ostree install ซึ่งเรียกว่า package layering จะสร้าง new deployment ที่รวม base OS กับ packages ที่เพิ่ม แต่แนะนำให้ใช้ containers แทนเมื่อเป็นไปได้ เพราะ layered packages ทำให้ update ช้าลงและเพิ่ม complexity ใช้ rpm-ostree สำหรับ system-level tools เท่านั้น เช่น htop, tmux, strace

Q: Fedora CoreOS กับ Flatcar Container Linux ต่างกันอย่างไร?

A: FCOS ใช้ rpm-ostree package manager อ้างอิง Fedora ecosystem ใช้ Ignition สำหรับ provisioning มี Zincati สำหรับ auto-updates Flatcar ใช้ A/B partition scheme สำหรับ updates ไม่มี package manager (immutable 100%) ใช้ Container Linux Config สำหรับ provisioning FCOS flexible กว่า (layer packages ได้) Flatcar minimal กว่าและ proven ใน production (ex-CoreOS)

Q: ใช้ Immutable OS กับ Kubernetes อย่างไร?

A: เหมาะมากเพราะ Kubernetes nodes ไม่ควรมี state ทั้งหมดอยู่ใน containers FCOS ใช้กับ k3s, k0s หรือ kubeadm ได้ Talos Linux ออกแบบมาเฉพาะสำหรับ Kubernetes nodes (API-managed, no SSH) Bottlerocket ของ AWS ออกแบบสำหรับ EKS nodes auto-updates ทำให้ nodes ได้ security patches อัตโนมัติ

Q: จะเริ่มสร้าง open source community อย่างไร?

A: เริ่มจาก documentation ที่ดี (README, CONTRIBUTING, CODE_OF_CONDUCT) สร้าง good-first-issues สำหรับ newcomers ตั้ง communication channels (GitHub Discussions, Discord) respond ทุก issue/PR ภายใน 48 ชั่วโมง recognize contributors ทุกู้คืน (ไม่ใช่แค่ code) เขียน blog posts เกี่ยวกับ project และ share ใน communities ที่เกี่ยวข้อง ความสม่ำเสมอสำคัญกว่า viral moment

📖 บทความที่เกี่ยวข้อง

Immutable OS Fedora CoreOS Remote Work Setupอ่านบทความ → Immutable OS Fedora CoreOS Network Segmentationอ่านบทความ → Immutable OS Fedora CoreOS GitOps Workflowอ่านบทความ → Immutable OS Fedora CoreOS SaaS Architectureอ่านบทความ →

📚 ดูบทความทั้งหมด →