نشر حاويات Docker
Docker هو ثورة في النشر—ابنأ مرة واحدة، شغّل في أي مكان، وودع عذر "يعمل على جهازي".
1. ما ستتعلمه
- بناء Dockerfile متعدد المراحل: صورة البناء + صورة التشغيل (Distroless / Eclipse Temurin)
- تحسين
.dockerignoreواستراتيجيات تخزين طبقات الصورة المؤقت - تنسيق Docker Compose: OrderFlow وMySQL وRedis—ثلاث خدمات تعمل معًا
- حقن متغيرات البيئة وتخريد التهيئة
- Bob بنى صورة Docker إنتاجية لـ OrderFlow بحجم أقل من 150 ميغابايت
2. قصة حقيقية لمهندس عمليات
(1) نقطة الألم: بيئة غير متسقة
بعد أن انتهى Bob من تصحيح OrderFlow محليًا ونشرها في الإنتاج، فشلت الخدمة في البدء بسبب عدم تطابق إصدارات مشغل MySQL واختلاف إصدارات JDK واختلاف تهيئات المنطقة الزمنية. كل نشر يتطلب نصف يوم لاستكشاف اختلافات البيئة، وأصبحت عبارة "يعمل على جهازي" شعار الفريق. لأسوأ من ذلك، فإن الخوادم الثلاثة تتطلب ثلاث عمليات نشر يدوية منفصلة، وغالبًا ما يتم تجاهل التهيئات.
(2) حل Docker
يقوم Docker بتعبئة التطبيق وجميع تبعياته في صورة واحدة:
FROM eclipse-temurin:17-jre-alpine
COPY target/orderflow-service.jar /app/app.jar
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
انشر بأمر واحد، مما يضمن بيئة متسقة بالكامل.
(3) الإيرادات
بعد أن بدأ Bob في استخدام Docker، تم بناء الصورة محليًا مرة واحدة، وجميع الخوادم الثلاثة سحبت وشغلت من نفس المصدر، مما ألغى عدم اتساق البيئة. أمر Docker Compose واحد يبدأ البيئة الكاملة—OrderFlow وMySQL وRedis—ويمكن للموظفين الجدد تشغيلها محليًا في خمس دقائق فقط.
3. Dockerfile متعدد المراحل
(1) عملية البناء متعددة المراحل
graph TD
A["المرحلة 1: البناء<br/>Maven + JDK 17<br/>ترجمة + تغليف"] --> B["orderflow-service.jar"]
B --> C["المرحلة 2: التشغيل<br/>JRE 17 Alpine<br/>نسخ JAR فقط"]
C --> D["الصورة النهائية<br/>~150MB"]
(1) ▶ مثال: Dockerfile متعدد المراحل
# المرحلة 1: البناء
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
# نسخ pom.xml أولاً لتخزين التبعيات مؤقتًا
COPY pom.xml .
RUN mvn dependency:go-offline -B
# نسخ المصدر والبناء
COPY src ./src
RUN mvn package -DskipTests -B
# المرحلة 2: التشغيل
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# إنشاء مستخدم غير جذر
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# نسخ JAR من مرحلة البناء
COPY --from=builder /build/target/*.jar app.jar
# تعيين الملكية
RUN chown -R appuser:appgroup /app
USER appuser
# كشف المنفذ
EXPOSE 8080
# فحص الصحة
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
# تشغيل التطبيق
ENTRYPOINT ["java", \
"-XX:+UseG1GC", \
"-XX:MaxRAMPercentage=75.0", \
"-jar", "app.jar"]
الناتج:
// التنفيذ ناجح
| الصورة الأساسية | الحجم | الأمان | حالات الاستخدام |
|---|---|---|---|
eclipse-temurin:17 |
~450MB | متوسط | التطوير/الاختبار |
eclipse-temurin:17-jre-alpine |
~170MB | متوسط-عالي | موصى به للإنتاج |
gcr.io/distroless/java17-debian12 |
~100MB | الأقصى | الأمان المطلق |
(2) ▶ مثال: صورة Distroless
# المرحلة 1: البناء
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# المرحلة 2: تشغيل Distroless
FROM gcr.io/distroless/java17-debian12
COPY --from=builder /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
الناتج:
// التنفيذ ناجح
docker exec -it للدخول في وضع التصحيح. يوصى بها للإنتاج، لكن استخدم صورة Alpine للتصحيح.
4. .dockerignore وتحسين الصورة
(1) ▶ مثال: .dockerignore
# .dockerignore
.git
.github
.idea
*.md
target/
!target/*.jar
node_modules/
*.log
.env
docker-compose*.yml
Dockerfile*
| استراتيجية التحسين | التأثير | الوصف |
|---|---|---|
.dockerignore استبعاد الملفات غير الضرورية |
تقليل سياق البناء | عدم إرسال .git و.idea إلخ |
| نسخ pom.xml أولاً | تخزين التبعيات مؤقتًا | تخطي التنزيل إذا لم تتغير التبعيات |
| بناء متعدد المراحل | تقليل حجم الصورة النهائية | لا يتضمن Maven أو المصدر |
| JRE بدلاً من JDK | توفير ~200MB | لا حاجة لأدوات الترجمة في وقت التشغيل |
| صورة أساسية Alpine | انخفاض بمقدار ~280MB | نظام Linux مصغر باستخدام musl libc |
5. تنسيق Docker Compose متعدد الخدمات
(1) ▶ مثال: OrderFlow + MySQL + Redis
# docker-compose.yml
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
DB_USERNAME: orderflow
DB_PASSWORD: ${DB_PASSWORD:-orderflow123}
REDIS_HOST: redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 3s
retries: 5
restart: unless-stopped
mysql:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: orderflow
MYSQL_USER: orderflow
MYSQL_PASSWORD: ${DB_PASSWORD:-orderflow123}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root123}
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
volumes:
mysql-data:
redis-data:
الناتج:
CONTAINER ID IMAGE STATUS PORTS
abc123 nginx:latest Up 2 hours 0.0.0.0:80->80/tcp
(2) ▶ مثال: البدء والتحقق
# بناء وبدء جميع الخدمات
docker compose up -d --build
# التحقق من حالة الخدمات
docker compose ps
# عرض سجلات التطبيق
docker compose logs -f app
# اختبار واجهة برمجة التطبيقات
curl http://localhost:8080/actuator/health
# إيقاف جميع الخدمات
docker compose down -v
الناتج:
CONTAINER ID IMAGE STATUS
abc123 latest Up 2 hours
| الأمر | الوصف |
|---|---|
docker compose up -d |
البدء في الخلفية |
docker compose down |
الإيقاف وحذف الحاوية |
docker compose logs -f app |
عرض سجلات التطبيق |
docker compose ps |
عرض حالة الخدمات |
docker compose build |
بناء الصورة فقط |
6. حقن متغيرات البيئة
(1) ▶ مثال: تخريد التهيئة
# application-prod.yml (في صورة Docker)
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:3306/orderflow
username: ${DB_USERNAME:orderflow}
password: ${DB_PASSWORD:defaultpass}
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
| طريقة التهيئة | الأولوية | السيناريوهات المطبقة |
|---|---|---|
Docker Compose environment |
عالية | نشر سهل |
ملف .env |
متوسط | التطوير المحلي |
| K8s ConfigMap / Secret | عالية | الإنتاج |
# ملف .env
DB_PASSWORD=secure_password_here
DB_ROOT_PASSWORD=root_secure_here
REDIS_PASSWORD=redis_secure_here
7. مثال شامل: Dockerization الكامل لـ OrderFlow
# Dockerfile
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
FROM eclipse-temurin:17-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080 8081
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-XX:+UseG1GC", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
# docker-compose.yml
version: "3.9"
services:
app:
build: .
ports:
- "8080:8080"
- "8081:8081"
environment:
SPRING_PROFILES_ACTIVE: prod
DB_HOST: mysql
DB_USERNAME: orderflow
DB_PASSWORD: ${DB_PASSWORD:-orderflow123}
REDIS_HOST: redis
MANAGEMENT_SERVER_PORT: "8081"
depends_on:
mysql: { condition: service_healthy }
redis: { condition: service_healthy }
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: orderflow
MYSQL_USER: orderflow
MYSQL_PASSWORD: ${DB_PASSWORD:-orderflow123}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root123}
volumes: [mysql-data:/var/lib/mysql]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s; timeout: 5s; retries: 10
redis:
image: redis:7-alpine
volumes: [redis-data:/data]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s; timeout: 3s; retries: 5
volumes:
mysql-data:
redis-data:
❓ أسئلة شائعة
docker compose logs -f app عرض السجلات؛ 2) docker compose exec app sh دخول الحاوية (صورة Alpine)؛ 3) تصحيح عن بُعد: ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"].depends_on وhealthcheck؟depends_on يضمن فقط ترتيب بدء الحاويات؛ ولا يضمن أن الخدمة جاهزة. عند استخدامه مع condition: service_healthy، سينتظر Docker Compose نجاح فحص الصحة قبل بدء الخدمة المعالة.mvn dependency:go-offline للاستفادة من تخزين طبقات Docker المؤقت؛ 2) استخدم .dockerignore لاستبعاد الملفات غير ذات الصلة؛ 3) استخدم تركيب ذاكرة التخزين المؤقت BuildKit.ndots). يوصى بـ Alpine لسيناريوهات JRE فقط.📖 ملخص
- Dockerfile متعدد المراحل: الترجمة خلال مرحلة البناء + مرحلة التشغيل تحتوي فقط على JAR وJRE
.dockerignoreيستبعد الملفات غير ذات الصلة؛ نسخ pom.xml أولاً لاستخدام طبقة التخزين المؤقت- Docker Compose لتنسيق الخدمات المتعددة: تطبيق + MySQL + Redis، مع فحوصات الصحة للتحكم في ترتيب البدء
- تهيئة حقن متغيرات البيئة؛ تخزين المعلومات الحساسة في ملف .env أو K8s Secret
- لبناء الصور، نوصي بـ Eclipse Temurin JRE Alpine (~170 MB) أو Distroless (~100 MB)
- التشغيل كمستخدم غير جذر؛ HEALTHCHECK ضروري للإنتاج
📝 تمارين
-
تمرين أساسي (الصعوبة ⭐): اكتب Dockerfile لـ OrderFlow، وابنِ الصورة، وشغّلها محليًا، وتحقق من أن
/actuator/healthيُرجع "UP". -
تمرين متقدم (الصعوبة: ⭐⭐): اكتب ملف
docker-compose.ymlلتنسيق الخدمات الثلاث—OrderFlow وMySQL وRedis—وقم بتهيئةhealthcheckوdepends_onلضمان بدء الخدمات بالترتيب الصحيح. -
تحدٍ (الصعوبة: ⭐⭐⭐): قم بتحسين الصورة لتكون أقل من 150 MB، واستخدم الصورة الأساسية Distroless، وقم بتهيئة معلمات ذاكرة JVM (
MaxRAMPercentage)، وأضف مستخدمًا غير جذر وتهيئات متعلقة بالأمان.



