Go: نشر لغة Go باستخدام Docker

آخر تحديث: 2026-08-26

إن التجميع الثابت في لغة Go يجعلها اللغة المثالية لعمليات النشر في الحاويات — حيث يُنتج ملف ثنائي واحد بالإضافة إلى صورة أولية صورة إنتاجية بحجم 12 ميغابايت.

عندما تحتاج إلى نشر واجهة برمجة تطبيقات (API) بلغة Go في بيئة الإنتاج، يمكن أن يساعدك ملف Dockerfile المصمم جيدًا على تقليل الحجم من صورة أساسية تبلغ 1.2 جيجابايت إلى 12 ميجابايت.

1. ستتعلم



2. قصة حقيقية لمهندس برمجيات الخلفية

(1) المشكلات: يستغرق نشر صورة Docker بحجم 1.2 جيجابايت 5 دقائق في كل مرة

واجهة برمجة تطبيقات التجارة الإلكترونية الخاصة بـ«بوب» جاهزة للانطلاق:

"لقد استخدمت الطريقة الأكثر ملاءمة: استخدمت FROM golang:1.22 كصورة أساسية، ثم أدرجت COPY شفرة المصدر فيها، وقمت بتجميعها داخل الحاوية. يبلغ حجم الصورة 1.2 جيجابايت، ويستغرق سحبها 5 دقائق في كل عملية نشر. ويستغرق مسار CI/CD 15 دقيقة من لحظة الإرسال حتى النشر. قال مديري: "عملية النشر بطيئة جدًّا — يستغرق التراجع عن التغييرات 10 دقائق.""

DOCKERFILE
# Bad approach: compile inside container, keep all build tools
FROM golang:1.22          # 800MB + compiler tools
WORKDIR /app
COPY . .
RUN go build -o server .
EXPOSE 8080
CMD ["./server"]          # Image 1.2GB! Includes compiler, dependencies, toolchain

(2) حل لعبة الغو: عملية بناء متعددة المراحل

DOCKERFILE
# Good approach: multi-stage build
# Stage 1: compile (use full Go image)
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .

# Stage 2: run (use minimal image)
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]            # Image 12MB! Only the binary

(3) النتائج: قبل التحسين وبعده

النظام المتري أحادي المرحلة (Go: 1.22) متعدد المراحل (Scratch) التحسن
حجم الصورة 1.2 جيجابايت 12 ميغابايت 100x
عملية سحب النشر 5 دقائق 5 ثوانٍ 60 مرة
المخاطر الأمنية تشمل المُجمِّعات وسلاسل الأدوات ثنائي فقط سطح هجوم ضئيل
ذاكرة التخزين المؤقتة للتركيب ❌ تجميع كامل في كل مرة ✅ التخزين المؤقت المتدرج للتبعيات


3. أفضل الممارسات في استخدام ملف Dockerfile

▶ مثال: ملف Dockerfile لـ Go متعدد المراحل

DOCKERFILE
# ===== Stage 1: Build =====
FROM golang:1.22-alpine AS builder

# Set working directory
WORKDIR /app

# Copy dependency files first (leverage Docker cache)
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Static compilation
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .

# ===== Stage 2: Run =====
FROM scratch

# Copy binary from builder stage
COPY --from=builder /app/server /server

# If timezone files are needed
# COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo

# If SSL certificates are needed
# COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD ["/server", "-health"]

CMD ["/server"]

▶ مثال: النسخة الألبية

DOCKERFILE
# ===== Stage 1: Build =====
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .

# ===== Stage 2: Run (Alpine) =====
FROM alpine:3.19

# Install runtime dependencies (if needed)
# RUN apk --no-cache add ca-certificates tzdata

COPY --from=builder /app/server /server

EXPOSE 8080
CMD ["/server"]

(3) اختيار الصورة الأساسية

الصورة الحجم الأمان حالات الاستخدام
scratch 0 ميغابايت ✅ سطح هجوم محدود مُجمَّع بشكل ثابت بلغة Go الخالصة، دون أي تبعيات خارجية
alpine 5 ميغابايت ⚠️ musl libc يتطلب وجود شل، وبرنامج curl، وشهادات، وما إلى ذلك
distroless 20 ميغابايت ✅ إصدار مبسط + أدوات يتطلب شهادة SSL وبيانات المنطقة الزمنية
golang:alpine 350 ميغابايت ❌ مخصص للتطوير مخصص لمرحلة البناء فقط
golang:1.22 800 ميغابايت لا تستخدم أبدًا في بيئة الإنتاج
💡 نصيحة: يمكن لـ -ldflags="-s -w" تقليل حجم الملف الثنائي: حيث يزيل -s جدول الرموز، بينما يزيل -w معلومات التصحيح DWARF. ويمكن أن يؤدي ذلك إلى تقليل حجم الملف الثنائي بنسبة إضافية تتراوح بين 30 و40٪ دون التأثير على أدائه.



4. الترجمة المتقاطعة

▶ مثال: برنامج نصي للتحويل البرمجي المتبادل

MAKEFILE
# Makefile
APP=server

.PHONY: build-all

# Build for current platform
build:
	CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/$(APP) .

# Cross-compile for multiple platforms
build-all:
	# Linux amd64 (most common)
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/$(APP)-linux-amd64 .
	# Linux arm64 (AWS Graviton / Apple M1)
	CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o bin/$(APP)-linux-arm64 .
	# macOS
	CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o bin/$(APP)-darwin-amd64 .
	# Windows
	CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o bin/$(APP)-windows-amd64.exe

# Docker build
docker-build:
	docker build -t myapp:latest .
▶ جرّب الكود

(2) تأثير CGO_ENABLED

CGO_ENABLED المزايا العيوب
=0 مُجمَّع بشكل ثابت، يعمل على أي نظام أساسي، صورة صغيرة الحجم لا يمكن استخدام مكتبات لغة C (مثل برنامج تشغيل C الخاص بـ SQLite)
=1 (الافتراضي) مكتبة C متوفرة تتطلب بيئة تشغيل C؛ تزيد من حجم الصورة
🔥 خطأ شائع: إذا كان كود Go الخاص بك يستخدم mattn/go-sqlite3 (برنامج تشغيل C)، فإن تعيين CGO_ENABLED=0 سيؤدي إلى فشل عملية الترجمة. الحل: استخدم برنامج تشغيل SQLite خاص بـ Go (مثل modernc.org/sqlite)، أو اضبط CGO_ENABLED=1 مع الصورة alpine (يتطلب تثبيت gcc و musl-dev).



5. استخدام Docker Compose لخدمات متعددة

▶ مثال: واجهة برمجة تطبيقات التجارة الإلكترونية + قاعدة البيانات + Redis

YAML
# docker-compose.yml
version: '3.8'

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
      - DB_PORT=3306
      - DB_USER=app
      - DB_PASSWORD=secret
      - DB_NAME=shop
      - REDIS_ADDR=redis:6379
      - GIN_MODE=release
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "/server", "-health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: shop
      MYSQL_USER: app
      MYSQL_PASSWORD: secret
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

volumes:
  db_data:


6. نشر K8s

▶ مثال: النشر الأساسي لـ K8s

YAML
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-api
  labels:
    app: go-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: go-api
  template:
    metadata:
      labels:
        app: go-api
    spec:
      containers:
      - name: api
        image: myregistry/go-api:latest
        ports:
        - containerPort: 8080
        env:
        - name: DB_HOST
          value: "mysql-service"
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secret
              key: password
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 3
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: go-api-service
spec:
  selector:
    app: go-api
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer
100%
flowchart TD
    subgraph Build ["Build Phase"]
        SRC[Source code] --> DEP[go mod download]
        DEP --> BUILD[go build]
        BUILD --> BIN[Binary 15MB]
    end
    subgraph Container ["Container Phase"]
        BIN --> CP[COPY to scratch]
        CP --> IMG[Image 12MB]
    end
    subgraph Deploy ["Deploy Phase"]
        IMG --> PUSH[Push to Registry]
        PUSH --> K8S[K8s Deployment]
        K8S --> POD[Pod 3 replicas]
    end
    Build --> Container --> Deploy


7. مثال كامل: العملية الكاملة لتحويل واجهة برمجة تطبيقات (API) للتجارة الإلكترونية إلى حاوية

▶ مثال: واجهة برمجة تطبيقات كاملة مع فحص الحالة

GO 📖 للعرض فقط
// cmd/server/main.go (complete API with health check)
package main

import (
    "context"
    "encoding/json"
    "flag"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    healthFlag := flag.Bool("health", false, "run health check")
    flag.Parse()

    if *healthFlag {
        // Health check mode: check if service is reachable
        resp, err := http.Get("http://localhost:8080/health")
        if err != nil {
            os.Exit(1)
        }
        resp.Body.Close()
        os.Exit(0)
    }

    mux := http.NewServeMux()

    // Health check endpoint
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })

    mux.HandleFunc("GET /ready", func(w http.ResponseWriter, r *http.Request) {
        // Check if dependencies like database are ready
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"ready": "true"})
    })

    // Business endpoint
    mux.HandleFunc("GET /api/products", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]interface{}{
            "products": []string{"laptop", "mouse", "keyboard"},
        })
    })

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    // Graceful shutdown
    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
        <-sigCh
        log.Println("Shutting down...")
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        defer cancel()
        server.Shutdown(ctx)
    }()

    log.Println("Service listening on :8080")
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
}
56 سطر من الكود المنطقي (تجاوز الحد 40, للعرض فقط)

(2) ملف Dockerfile المصاحب

DOCKERFILE
# Dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app

# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download

COPY . .

# Static compilation + strip debug info
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# === Run stage ===
FROM scratch

# Copy binary
COPY --from=builder /app/server /server

# If SSL certificates are needed (accessing external HTTPS APIs)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD ["/server", "-health"]

CMD ["/server"]

(3) ملف .dockerignore

TEXT 📖 للعرض فقط
# .dockerignore
.git
.gitignore
*.md
bin/
tmp/
Dockerfile
.dockerignore
💡 نصيحة: .dockerignore له تأثير كبير على سرعة عملية البناء — فهو يستبعد الملفات غير الضرورية، مما يقلل من حجم السياق المرسل إلى برنامج Docker الخفي. وإضافة الدليل .git (الذي قد يصل حجمه إلى مئات الميغابايت) يمكن أن تسرع عملية البناء بشكل ملحوظ.



❓ أسئلة شائعة

س لماذا يؤدي «البناء متعدد المراحل» إلى تقليل حجم الصورة؟
ج تستخدم المرحلة الأولى (البناء) صورة Go الكاملة لترجمة الملفات الثنائية، بينما تقتصر المرحلة الثانية (التشغيل) على نسخ الملفات الثنائية المترجمة إلى صورة أساسية مصغرة. ولا تظهر أدوات البناء (المترجمات، ومديرو التبعيات، والكود المصدري) في الصورة النهائية — بل تظهر الملفات الثنائية نفسها فقط.
س هل صورة «scratch» آمنة؟
ج تُعد صورة «scratch» الأكثر أمانًا — فهي فارغة تمامًا، ولا تحتوي على أي شل أو مكتبات أو أدوات. ولا يمكن للمهاجم تنفيذ أي أوامر داخل الحاوية. ومع ذلك، إذا كان برنامجك يتطلب شهادة SSL أو بيانات المنطقة الزمنية أو شل، فستحتاج إلى نسخ هذه الملفات من مرحلة البناء أو التبديل إلى alpine/distroless.
س ما معنى CGO_ENABLED=0؟
ج بشكل افتراضي، تستخدم لغة Go الارتباط الديناميكي (CGO_ENABLED=1، باستخدام بيئة تشغيل C). يؤدي تعيين CGO_ENABLED=0 إلى قيام Go بإنشاء ملف ثنائي ثابت تمامًا — أي ملف لا يعتمد على أي مكتبات خارجية. وهذا يسمح بتشغيل الملف الثنائي على أي نظام Linux، بما في ذلك scratch.
س كيف يمكنني استخدام الترجمة المتقاطعة؟
ج قم بتعيين متغيرات البيئة GOOS (نظام التشغيل الهدف) وGOARCH (البنية الهدف). GOOS=linux GOARCH=amd64 go build يقوم بترجمة ملف ثنائي لنظام Linux على نظام macOS. تدعم لغة Go جميع تركيبات المنصات تقريبًا. وتكون الترجمة المتقاطعة في أبسط صورها عند CGO_ENABLED=0.
س ما الفرق بين فحص الحالة (health check) واختبار الجاهزية (readiness probe)؟
ج يتحقق فحص الحالة من سلامة العملية (process) — وإذا لم تكن سليمة، يقوم K8s بإعادة تشغيل البود (Pod). أما اختبار الجاهزية فيتحقق مما إذا كانت الخدمة جاهزة لقبول حركة المرور — وإذا لم تكن جاهزة، يقوم K8s بإزالة البود من الخدمة. استخدم اختبارات الجاهزية خلال مرحلة بدء التشغيل واختبارات الاستمرارية خلال مرحلة التشغيل.
س ما هي العلاقة بين Docker و K8s؟ هل أحتاج إلى تعلم K8s؟
ج Docker هو بيئة تشغيل للحاويات — حيث يقوم بتجميع التطبيقات في وحدات موحدة. أما K8s فهو منصة لتنسيق الحاويات — حيث يدير عمليات النشر والتوسع وفحوصات سلامة الحاويات المتعددة. تحتاج المشاريع الصغيرة إلى Docker (أو Docker Compose) فقط، بينما تتطلب المشاريع الكبيرة استخدام K8s. تغطي هذه الدورة أمثلة على تنسيق K8s فقط ولا تتعمق في مفاهيم K8s.
س ما هو الحجم المعتاد لصور Docker الخاصة بلغة Go؟
ج الملفات الثنائية الثابتة لـ Go + مساحة التخزين المؤقتة ≈ 10–20 ميغابايت. تطبيقات Python (استنادًا إلى python:3.12) ≈ 300 ميغابايت. تطبيق Node.js (استنادًا إلى node:22) ≈ 400 ميغابايت. تبلغ مساحة تطبيق Java (استنادًا إلى amazoncorretto:21) ≈ 500 ميغابايت. تتمتع صور Go بميزة كبيرة من حيث الحجم.

📖 ملخص


📝 تمارين

  1. أساسي (مستوى الصعوبة ⭐): اكتب ملف Dockerfile (بناء متعدد المراحل) لخدمة HTTP بسيطة بلغة Go. قم ببناء الصورة وتحقق من إمكانية الوصول إليها باستخدام docker run -p 8080:8080. استخدم docker images للتحقق من حجم الصورة.

  2. متقدم (درجة الصعوبة ⭐⭐): تنفيذ التحويل الكامل لتطبيق Go إلى حاويات مع قاعدة بيانات. المتطلبات: (1) ثلاث خدمات: واجهة برمجة تطبيقات Go، وMySQL، وRedis؛ (2) تنسيق ملف docker-compose.yml؛ (3) تنفيذ فحص حالة للتأكد من جاهزية قاعدة البيانات وRedis قبل بدء تشغيل واجهة برمجة التطبيقات؛ (4) استخدام os.Getenv في كود Go لقراءة معلومات اتصال قاعدة البيانات؛ (5) استخدام .dockerignore لاستبعاد الملفات غير الضرورية.

  3. التحدي (الصعوبة ⭐⭐⭐): تنفيذ خط أنابيب CI/CD كامل (تنفيذ مفاهيمي؛ لا يلزم وجود CI فعلي). المتطلبات: (1) يجب أن يدعم ملف Makefile make build (التجميع المتقاطع لنظامي linux/amd64 و linux/arm64)، وmake docker-build، وmake docker-push؛ (2) عمليات بناء Dockerfile متعددة المراحل؛ (3) تحسين ذاكرة التخزين المؤقتة (cache) لعملية البناء لكل مرحلة؛ (4) K8s deployment.yaml + service.yaml؛ (5) تضم اختبارات الفعالية والجاهزية؛ (6) حدود الموارد (requests و limits).

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%