容器化与编排¶
学习目标¶
通过本文档的学习,你将能够:
- 理解核心概念和原理
- 掌握实际应用方法
- 了解最佳实践和注意事项
前置知识¶
在学习本文档之前,建议你已经掌握:
- 基础的嵌入式系统知识
- C/C++编程基础
- 相关领域的基本概念
概述¶
容器化技术为医疗器械软件提供了一致的运行环境,简化了部署和扩展。Docker和Kubernetes是容器化和编排的事实标准,但在医疗器械领域需要特别考虑合规性、安全性和可追溯性。
Docker容器化¶
Docker基础¶
Docker是一个开源的容器化平台,允许将应用及其依赖打包到轻量级、可移植的容器中。
核心概念: - 镜像(Image): 只读模板,包含应用和依赖 - 容器(Container): 镜像的运行实例 - Dockerfile: 定义镜像构建步骤的文本文件 - Registry: 存储和分发镜像的仓库
医疗器械软件Dockerfile示例¶
# 多阶段构建 - 构建阶段
FROM gcc:11 AS builder
# 设置工作目录
WORKDIR /app
# 复制源代码
COPY src/ ./src/
COPY CMakeLists.txt ./
# 安装构建依赖
RUN apt-get update && apt-get install -y \
cmake \
libssl-dev \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# 构建应用
RUN mkdir build && cd build && \
cmake -DCMAKE_BUILD_TYPE=Release .. && \
cmake --build . --parallel 4
# 运行阶段 - 最小化镜像
FROM debian:bullseye-slim
# 添加元数据标签
LABEL maintainer="dev@medical-device.com" \
version="1.0.0" \
description="Medical Device Software" \
regulatory.fda.class="II" \
regulatory.iec62304.class="B"
# 创建非root用户
RUN groupadd -r medapp && useradd -r -g medapp medapp
# 安装运行时依赖
RUN apt-get update && apt-get install -y \
libssl1.1 \
libpq5 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# 复制构建产物
COPY --from=builder /app/build/bin/medical-app /usr/local/bin/
COPY --from=builder /app/build/lib/*.so /usr/local/lib/
# 复制配置文件
COPY config/ /etc/medical-app/
# 设置权限
RUN chown -R medapp:medapp /etc/medical-app && \
chmod 755 /usr/local/bin/medical-app
# 切换到非root用户
USER medapp
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# 启动应用
ENTRYPOINT ["/usr/local/bin/medical-app"]
CMD ["--config", "/etc/medical-app/config.yaml"]
Docker最佳实践¶
1. 多阶段构建¶
减小镜像大小,提高安全性:
# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 运行阶段
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "dist/main.js"]
2. 最小化镜像层¶
合并RUN命令,减少层数:
# 不推荐
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
# 推荐
RUN apt-get update && apt-get install -y \
package1 \
package2 \
&& rm -rf /var/lib/apt/lists/*
3. 使用.dockerignore¶
排除不必要的文件:
# .dockerignore
.git
.gitignore
*.md
tests/
docs/
*.log
.vscode/
.idea/
node_modules/
__pycache__/
*.pyc
4. 安全扫描¶
使用工具扫描镜像漏洞:
# Trivy扫描
trivy image medical-device:latest
# Clair扫描
clair-scanner medical-device:latest
# Docker Scout扫描
docker scout cves medical-device:latest
Docker Compose¶
用于定义和运行多容器应用:
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
image: medical-device:latest
container_name: medical-app
ports:
- "8080:8080"
environment:
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=medicaldb
- REDIS_HOST=redis
depends_on:
- postgres
- redis
volumes:
- ./config:/etc/medical-app:ro
- app-logs:/var/log/medical-app
networks:
- medical-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 3s
retries: 3
postgres:
image: postgres:13-alpine
container_name: medical-db
environment:
- POSTGRES_DB=medicaldb
- POSTGRES_USER=meduser
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
secrets:
- db_password
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- medical-network
restart: unless-stopped
redis:
image: redis:6-alpine
container_name: medical-cache
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis-data:/data
networks:
- medical-network
restart: unless-stopped
nginx:
image: nginx:alpine
container_name: medical-proxy
ports:
- "443:443"
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- medical-network
restart: unless-stopped
volumes:
postgres-data:
redis-data:
app-logs:
networks:
medical-network:
driver: bridge
secrets:
db_password:
file: ./secrets/db_password.txt
Kubernetes编排¶
Kubernetes基础¶
Kubernetes(K8s)是一个开源的容器编排平台,用于自动化容器化应用的部署、扩展和管理。
核心概念: - Pod: 最小部署单元,包含一个或多个容器 - Deployment: 管理Pod的副本和更新 - Service: 为Pod提供稳定的网络访问 - ConfigMap: 存储配置数据 - Secret: 存储敏感信息 - Namespace: 资源隔离和组织
医疗器械应用部署配置¶
Deployment配置¶
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: medical-device
namespace: production
labels:
app: medical-device
version: v1.0.0
regulatory.class: "II"
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: medical-device
template:
metadata:
labels:
app: medical-device
version: v1.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
spec:
# 安全上下文
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
# 初始化容器
initContainers:
- name: init-db
image: busybox:1.35
command: ['sh', '-c', 'until nc -z postgres 5432; do echo waiting for db; sleep 2; done;']
# 应用容器
containers:
- name: medical-app
image: registry.medical-device.com/medical-device:v1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
protocol: TCP
# 环境变量
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: medical-config
key: db.host
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: medical-secrets
key: db.password
# 资源限制
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
# 健康检查
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
# 挂载卷
volumeMounts:
- name: config
mountPath: /etc/medical-app
readOnly: true
- name: logs
mountPath: /var/log/medical-app
- name: data
mountPath: /var/lib/medical-app
# 卷定义
volumes:
- name: config
configMap:
name: medical-config
- name: logs
emptyDir: {}
- name: data
persistentVolumeClaim:
claimName: medical-data-pvc
# 镜像拉取密钥
imagePullSecrets:
- name: registry-credentials
# 节点选择
nodeSelector:
workload: medical-device
# 容忍度
tolerations:
- key: "medical-device"
operator: "Equal"
value: "true"
effect: "NoSchedule"
# 亲和性
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- medical-device
topologyKey: kubernetes.io/hostname
Service配置¶
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: medical-device
namespace: production
labels:
app: medical-device
spec:
type: ClusterIP
selector:
app: medical-device
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
Ingress配置¶
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: medical-device
namespace: production
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
tls:
- hosts:
- medical-device.com
secretName: medical-device-tls
rules:
- host: medical-device.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: medical-device
port:
number: 80
ConfigMap配置¶
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: medical-config
namespace: production
data:
db.host: "postgres.production.svc.cluster.local"
db.port: "5432"
db.name: "medicaldb"
redis.host: "redis.production.svc.cluster.local"
redis.port: "6379"
log.level: "INFO"
app.config: |
server:
port: 8080
timeout: 30s
database:
max_connections: 100
connection_timeout: 5s
cache:
ttl: 3600
Secret配置¶
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: medical-secrets
namespace: production
type: Opaque
stringData:
db.password: "secure_password_here"
redis.password: "redis_password_here"
api.key: "api_key_here"
PersistentVolumeClaim配置¶
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: medical-data-pvc
namespace: production
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: fast-ssd
Helm图表¶
Helm是Kubernetes的包管理器,简化应用部署和管理。
Chart结构¶
medical-device/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-prod.yaml
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── pvc.yaml
│ ├── hpa.yaml
│ └── _helpers.tpl
└── charts/
Chart.yaml¶
apiVersion: v2
name: medical-device
description: Medical Device Software Helm Chart
type: application
version: 1.0.0
appVersion: "1.0.0"
keywords:
- medical-device
- healthcare
- fda
maintainers:
- name: DevOps Team
email: devops@medical-device.com
dependencies:
- name: postgresql
version: "11.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "16.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
values.yaml¶
# 默认配置值
replicaCount: 3
image:
repository: registry.medical-device.com/medical-device
pullPolicy: IfNotPresent
tag: "1.0.0"
imagePullSecrets:
- name: registry-credentials
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hosts:
- host: medical-device.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: medical-device-tls
hosts:
- medical-device.com
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
nodeSelector:
workload: medical-device
tolerations: []
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- medical-device
topologyKey: kubernetes.io/hostname
# 应用配置
config:
db:
host: "postgres"
port: 5432
name: "medicaldb"
redis:
host: "redis"
port: 6379
log:
level: "INFO"
# 数据库配置
postgresql:
enabled: true
auth:
username: meduser
password: ""
database: medicaldb
primary:
persistence:
enabled: true
size: 10Gi
# Redis配置
redis:
enabled: true
auth:
enabled: true
password: ""
master:
persistence:
enabled: true
size: 5Gi
部署命令¶
# 安装
helm install medical-device ./medical-device \
--namespace production \
--create-namespace \
--values values-prod.yaml
# 升级
helm upgrade medical-device ./medical-device \
--namespace production \
--values values-prod.yaml
# 回滚
helm rollback medical-device 1 --namespace production
# 卸载
helm uninstall medical-device --namespace production
自动扩缩容¶
Horizontal Pod Autoscaler (HPA)¶
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: medical-device-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: medical-device
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000"
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 2
periodSeconds: 30
selectPolicy: Max
Vertical Pod Autoscaler (VPA)¶
# vpa.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: medical-device-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: medical-device
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: medical-app
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 1000m
memory: 1Gi
controlledResources:
- cpu
- memory
医疗器械容器化特殊考虑¶
1. 合规性标签¶
在镜像和Pod中添加合规性元数据:
metadata:
labels:
regulatory.fda.class: "II"
regulatory.iec62304.class: "B"
regulatory.iso13485: "true"
version: "1.0.0"
build.number: "12345"
build.date: "2024-01-15"
2. 审计日志¶
确保所有操作都有审计日志:
# 启用Kubernetes审计日志
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
namespaces: ["production"]
verbs: ["create", "update", "patch", "delete"]
resources:
- group: ""
resources: ["pods", "services", "configmaps", "secrets"]
- group: "apps"
resources: ["deployments", "statefulsets"]
3. 数据持久化¶
医疗数据必须持久化存储:
# StatefulSet用于有状态应用
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: medical-database
spec:
serviceName: medical-database
replicas: 3
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "fast-ssd"
resources:
requests:
storage: 100Gi
4. 网络策略¶
限制Pod间通信:
# networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: medical-device-netpol
namespace: production
spec:
podSelector:
matchLabels:
app: medical-device
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: production
- podSelector:
matchLabels:
app: nginx-ingress
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
5. 备份和恢复¶
定期备份关键数据:
# cronjob-backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
namespace: production
spec:
schedule: "0 2 * * *" # 每天凌晨2点
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: postgres:13
command:
- /bin/sh
- -c
- |
pg_dump -h postgres -U meduser medicaldb | \
gzip > /backup/backup-$(date +%Y%m%d-%H%M%S).sql.gz
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: medical-secrets
key: db.password
volumeMounts:
- name: backup
mountPath: /backup
volumes:
- name: backup
persistentVolumeClaim:
claimName: backup-pvc
restartPolicy: OnFailure
容器安全最佳实践¶
1. 镜像安全¶
- 使用官方基础镜像
- 定期更新镜像
- 扫描漏洞
- 签名镜像
# 使用Docker Content Trust
export DOCKER_CONTENT_TRUST=1
docker push registry.medical-device.com/medical-device:v1.0.0
# 使用Cosign签名
cosign sign --key cosign.key registry.medical-device.com/medical-device:v1.0.0
2. 运行时安全¶
- 非root用户运行
- 只读根文件系统
- 禁用特权模式
- 限制能力(Capabilities)
3. 网络安全¶
- 使用网络策略
- 启用TLS加密
- 限制出站流量
- 使用服务网格(如Istio)
4. 密钥管理¶
- 使用Kubernetes Secrets
- 集成外部密钥管理(如Vault)
- 定期轮换密钥
- 加密静态数据
# 使用External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: medical-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: medical-secrets
creationPolicy: Owner
data:
- secretKey: db.password
remoteRef:
key: secret/data/medical-device/db
property: password
监控和日志¶
Prometheus监控¶
# servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: medical-device
namespace: production
spec:
selector:
matchLabels:
app: medical-device
endpoints:
- port: http
path: /metrics
interval: 30s
日志收集¶
# fluentd-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
namespace: kube-system
data:
fluent.conf: |
<source>
@type tail
path /var/log/containers/medical-device-*.log
pos_file /var/log/fluentd-medical-device.pos
tag kubernetes.medical-device
<parse>
@type json
time_key time
time_format %Y-%m-%dT%H:%M:%S.%NZ
</parse>
</source>
<filter kubernetes.medical-device>
@type record_transformer
<record>
regulatory_class "II"
environment "production"
</record>
</filter>
<match kubernetes.medical-device>
@type elasticsearch
host elasticsearch.logging.svc.cluster.local
port 9200
logstash_format true
logstash_prefix medical-device
</match>
故障排查¶
常用命令¶
# 查看Pod状态
kubectl get pods -n production
# 查看Pod日志
kubectl logs -f medical-device-xxx -n production
# 进入容器
kubectl exec -it medical-device-xxx -n production -- /bin/sh
# 查看事件
kubectl get events -n production --sort-by='.lastTimestamp'
# 查看资源使用
kubectl top pods -n production
kubectl top nodes
# 描述资源
kubectl describe pod medical-device-xxx -n production
常见问题¶
- ImagePullBackOff: 检查镜像名称和拉取密钥
- CrashLoopBackOff: 查看日志,检查健康检查配置
- Pending: 检查资源请求和节点容量
- OOMKilled: 增加内存限制
相关资源¶
参考文献¶
- Docker官方文档: https://docs.docker.com/
- Kubernetes官方文档: https://kubernetes.io/docs/
- Helm官方文档: https://helm.sh/docs/
- "Kubernetes in Action" - Marko Lukša
- "Docker Deep Dive" - Nigel Poulton
标签: Docker, Kubernetes, 容器化, 编排, Helm, 医疗器械
最后更新: 2024-01
💬 讨论区
欢迎在这里分享您的想法、提出问题或参与讨论。需要 GitHub 账号登录。