demo代码仓库:https://github.com/songxinjianqwe/capsule-demo-app

项目

该项目跑了一个简单的用户添加、查询的示例。

  • GET /users
    • 返回所有用户
  • GET /users/{userId}
    • 返回该用户信息,优先从Redis缓存中获取,没有命中则从DB拿
  • POST /users

    • body

      1. {
      2. "id": "tom",
      3. "nickName": "tom"
      4. }
    • 创建一个用户,然后放到Redis缓存中,并落库

      基于原生Docker+Link

      MySQL

  1. docker pull mysql
  2. docker run —name=mysql -e MYSQL_ROOT_PASSWORD=$ROOT_PASSWORD -p 3306:3306 -d mysql
  3. docker exec -it $CONTAINER_ID bash
  4. 创建database
  5. exit

    如何实现数据持久化(Volumn)

    -v /data:/var/lib/mysql
    /data是宿主机的文件目录。
    -v的意思就是把容器中的目录和宿主机中的目录做映射,我们只要把容器中mysql的数据目录映射到本地,将来就算这个容器被删除了,那么数据也还是在本地。

注意:
-v /data
是将宿主机下的/var/lib/docker下的某个目录映射到了容器的/data下。
这种方式在容器销毁后还会保留,但是宿主机的映射的目录是随机的,不方便重新挂载。

docker run —name=mysql
-v /host/mysql/conf:/etc/mysql/conf.d
-v /host/mysql/logs:/logs
-v /host/mysql/data:/var/lib/mysql
-e MYSQL_ROOT_PASSWORD=$ROOT_PASSWORD -p 3306:3306 -d mysql

  1. docker run --name=mysql -v /Users/jasper/Dev/data/mysql/data:/var/lib/mysql -v /Users/jasper/Dev/data/mysql/logs:/logs -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -d mysql

挂载了数据卷后,即使销毁容器,数据库里的数据也不会丢失,只要指定一个固定的宿主机目录即可(如果-v /var/lib/mysql也是可以挂载的,但是宿主机目录不是固定的,而是随机的,下次再次启动后不方便找到该目录重新挂载)
注意,首次创建容器后,需要创建一个名为demo的database(不需要手动建表,由JPA来自动建表)。之后重启容器后不需要重复此步骤,因为volume可以持久化数据。

Redis

docker pull redis
docker run -d -p 6379:6379 redis

可选:
-v ./host/redis/data:/data
-v /host/redis/config/redis.conf:/usr/local/etc/redis/redis.conf
CMD为redis-server /usr/local/etc/redis/redis.conf

  1. docker run --name=redis -v /Users/jasper/Dev/data/redis/data:/data -d -p 6379:6379 redis

Spring Boot

  1. FROM java:8
  2. VOLUME /tmp
  3. ADD capsule-demo-app.jar app.jar
  4. ENTRYPOINT [ "sh", "-c", "java -jar /app.jar"]
  1. docker run -d -p 8080:8080 -e "SPRING_PROFILES_ACTIVE=prod" --link mysql:mysql-container --link redis:redis-container --name capsule-demo-app capsule/capsule-demo-app

这里使用CMD来传入profile,指定为prod。
根据SpringBoot规范,我们会在src/main/resources下创建一个application-prod.yml/properties配置文件来指定profile为prod的特定配置,在这个配置文件中,我们会连接redis、mysql的url从localhost改为-link时候alias(冒号后的)。
—link默认是基于DNS实现的,在启动Spring Boot容器时,会将容器IP与容器别名放到/etc/hosts,这样我们只需要把连接的IP改为别名即可。

基于Kubernetes

不需要修改Spring Boot的Dockefile。
需要将之前docker run时指定的指令均落到Kubernetes的yaml配置文件中。

MySQL

创建一个单节点的MySQL,它依赖于Persistent-Volume。
这里创建了一个PersistentVolume和PersistentVolumeClaim。注意hostPath最好不要设置为用户目录,否则会出现权限问题。

  1. kind: PersistentVolume
  2. apiVersion: v1
  3. metadata:
  4. name: mysql-pv-volume
  5. labels:
  6. type: local
  7. spec:
  8. storageClassName: manual
  9. capacity:
  10. storage: 20Gi
  11. accessModes:
  12. - ReadWriteOnce
  13. hostPath:
  14. path: "/container"
  15. ---
  16. apiVersion: v1
  17. kind: PersistentVolumeClaim
  18. metadata:
  19. name: mysql-pv-claim
  20. spec:
  21. storageClassName: manual
  22. accessModes:
  23. - ReadWriteOnce
  24. resources:
  25. requests:
  26. storage: 20Gi

然后创建MySQL的Deployment。

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: mysql-deployment
  5. spec:
  6. replicas: 1
  7. selector:
  8. matchLabels:
  9. app: mysql
  10. template:
  11. metadata:
  12. labels:
  13. app: mysql
  14. spec:
  15. containers:
  16. - name: mysql
  17. image: mysql:5.6
  18. imagePullPolicy: IfNotPresent
  19. ports:
  20. - containerPort: 3306
  21. env:
  22. - name: MYSQL_ROOT_PASSWORD
  23. value: "123456"
  24. volumeMounts:
  25. - name: mysql-storage
  26. mountPath: /var/lib/mysql
  27. volumes:
  28. - name: mysql-storage
  29. persistentVolumeClaim:
  30. claimName: mysql-pv-claim

之后再创建Service,暴露为一个服务,注册DNS。

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: mysql-service
  5. spec:
  6. selector:
  7. app: mysql
  8. ports:
  9. - protocol: TCP
  10. port: 3306
  11. targetPort: 3306

Redis

创建一个Redis的Deployment。

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: redis-deployment
  5. spec:
  6. replicas: 1
  7. selector:
  8. matchLabels:
  9. app: redis
  10. template:
  11. metadata:
  12. labels:
  13. app: redis
  14. spec:
  15. containers:
  16. - name: redis
  17. image: redis
  18. imagePullPolicy: IfNotPresent
  19. ports:
  20. - containerPort: 6379

然后暴露为服务:

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: redis-service
  5. spec:
  6. selector:
  7. app: redis
  8. ports:
  9. - protocol: TCP
  10. port: 6379
  11. targetPort: 6379

Spring Boot

在运行App前,请先确保Redis和MySQL正确启动。
使用kubectl get pods可以查看Pod的状态,如果状态异常,可以使用kubectl describe pods $POD_ID来排查问题,也可以使用kubectl logs -f POD_ID来查看运行日志。

运行App需要注意几个问题:

  • image这里使用了本地镜像,否则每次有代码变更就要重新传到公开或私有的镜像仓库(比如阿里云),效率很低,使用本地镜像(即让minikube共享docker daemon的local repository)请按照以下步骤进行:
    • As the README describes, you can reuse the Docker daemon from Minikube with eval $(minikube docker-env).
    • So to use an image without uploading it, you can follow these steps:
  • set the environment variables with eval $(minikube docker-env)
  • build the image with the Docker daemon of Minukube (eg docker build -t my-image .)
  • set the image in the pod spec like the build tag (eg my-image)
  • set the imagePullPolicy to Never, otherwise Kubernetes will try to download the image.
  • Important note: You have to run eval $(minikube docker-env) on each terminal you want to use, since it only sets the environment variables for the current shell session.
  • 如果想让minikube使用本地镜像,需要做到:
    • 在当前shell中eval $(minikube docker-env)
    • docker build
    • 打标签,不能是latest
    • yaml里image pull policy不能用always
    • 在当前shell中kubectl create
      • 如何感知其他服务?
  • Docker是使用Link,而Link是通过修改当前容器的/etc/hosts实现的。
  • Kubernetes是使用环境变量+DNS的方式来实现的
    • 首先需要将Redis、MySQL等暴露为Service,之后会注册到DNS中
      • 可以使用nslookup命令来检查是否正确注册到DNS
      • 域名格式为service-name.namespace.svc.cluster.local
    • 将Spring Boot配置文件中原来的localhost改为${占位符},如${MYSQL_HOST},Spring会将该占位符作为key,从环境变量中取值,来拿到完整的域名
      1. apiVersion: apps/v1
      2. kind: Deployment
      3. metadata:
      4. name: capsule-demo-app-deployment
      5. spec:
      6. replicas: 3
      7. selector:
      8. matchLabels:
      9. app: capsule-demo-app
      10. template:
      11. metadata:
      12. labels:
      13. app: capsule-demo-app
      14. spec:
      15. containers:
      16. - name: capsule-demo-app
      17. image: capsule/capsule-demo-app:1.0
      18. imagePullPolicy: IfNotPresent
      19. ports:
      20. - containerPort: 8080
      21. env:
      22. - name: MYSQL_HOST
      23. # 使用环境变量+DNS的方式来访问redis、mysql等其他service暴露的服务
      24. # 这里环境变量的格式就是service-name.namespace.svc.cluster.local
      25. # 当redis、mysql暴露为服务后,会注册到DNS中,之后可以使用DNS的方式来访问服务
      26. # nslookup domain可以测试是否正确注册DNS
      27. value: mysql-service.default.svc.cluster.local
      28. - name: REDIS_HOST
      29. value: redis-service.default.svc.cluster.local
      30. - name: SPRING_PROFILES_ACTIVE
      31. value: 'prodk8s'
      注意下面的${MYSQL_HOST}和${REDIS_HOST}
      1. spring:
      2. datasource:
      3. url: jdbc:mysql://${MYSQL_HOST}:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
      4. username: root
      5. password: 123456
      6. driver-class-name: com.mysql.jdbc.Driver
      7. redis:
      8. host: ${REDIS_HOST}
      9. database: 0
      10. port: 6379
      11. password:

当Deployment启动OK后,暴露为服务:

  1. kind: Service
  2. apiVersion: v1
  3. metadata:
  4. name: capsule-demo-app-service
  5. spec:
  6. type: NodePort
  7. selector:
  8. app: capsule-demo-app
  9. ports:
  10. - protocol: TCP
  11. port: 8080
  12. targetPort: 8080
  13. nodePort: 32141

启动后,可以通过minikube service capsule-demo-app --url来拿到地址,也可以通过’kubectl cluster-info’拿到集群IP,然后端口号为NodePort(32141),这样就可以在宿主机上进行访问了服务。