demo代码仓库:https://github.com/songxinjianqwe/capsule-demo-app
项目
该项目跑了一个简单的用户添加、查询的示例。
- GET /users
- 返回所有用户
- GET /users/{userId}
- 返回该用户信息,优先从Redis缓存中获取,没有命中则从DB拿
POST /users
- docker pull mysql
- docker run —name=mysql -e MYSQL_ROOT_PASSWORD=$ROOT_PASSWORD -p 3306:3306 -d mysql
- docker exec -it $CONTAINER_ID bash
- 创建database
- 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
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
docker run --name=redis -v /Users/jasper/Dev/data/redis/data:/data -d -p 6379:6379 redis
Spring Boot
FROM java:8VOLUME /tmpADD capsule-demo-app.jar app.jarENTRYPOINT [ "sh", "-c", "java -jar /app.jar"]
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最好不要设置为用户目录,否则会出现权限问题。
kind: PersistentVolumeapiVersion: v1metadata:name: mysql-pv-volumelabels:type: localspec:storageClassName: manualcapacity:storage: 20GiaccessModes:- ReadWriteOncehostPath:path: "/container"---apiVersion: v1kind: PersistentVolumeClaimmetadata:name: mysql-pv-claimspec:storageClassName: manualaccessModes:- ReadWriteOnceresources:requests:storage: 20Gi
然后创建MySQL的Deployment。
apiVersion: apps/v1kind: Deploymentmetadata:name: mysql-deploymentspec:replicas: 1selector:matchLabels:app: mysqltemplate:metadata:labels:app: mysqlspec:containers:- name: mysqlimage: mysql:5.6imagePullPolicy: IfNotPresentports:- containerPort: 3306env:- name: MYSQL_ROOT_PASSWORDvalue: "123456"volumeMounts:- name: mysql-storagemountPath: /var/lib/mysqlvolumes:- name: mysql-storagepersistentVolumeClaim:claimName: mysql-pv-claim
之后再创建Service,暴露为一个服务,注册DNS。
kind: ServiceapiVersion: v1metadata:name: mysql-servicespec:selector:app: mysqlports:- protocol: TCPport: 3306targetPort: 3306
Redis
创建一个Redis的Deployment。
apiVersion: apps/v1kind: Deploymentmetadata:name: redis-deploymentspec:replicas: 1selector:matchLabels:app: redistemplate:metadata:labels:app: redisspec:containers:- name: redisimage: redisimagePullPolicy: IfNotPresentports:- containerPort: 6379
然后暴露为服务:
kind: ServiceapiVersion: v1metadata:name: redis-servicespec:selector:app: redisports:- protocol: TCPport: 6379targetPort: 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:
- As the README describes, you can reuse the Docker daemon from Minikube with
- 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
imagePullPolicytoNever, 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,从环境变量中取值,来拿到完整的域名
注意下面的${MYSQL_HOST}和${REDIS_HOST}apiVersion: apps/v1kind: Deploymentmetadata:name: capsule-demo-app-deploymentspec:replicas: 3selector:matchLabels:app: capsule-demo-apptemplate:metadata:labels:app: capsule-demo-appspec:containers:- name: capsule-demo-appimage: capsule/capsule-demo-app:1.0imagePullPolicy: IfNotPresentports:- containerPort: 8080env:- name: MYSQL_HOST# 使用环境变量+DNS的方式来访问redis、mysql等其他service暴露的服务# 这里环境变量的格式就是service-name.namespace.svc.cluster.local# 当redis、mysql暴露为服务后,会注册到DNS中,之后可以使用DNS的方式来访问服务# nslookup domain可以测试是否正确注册DNSvalue: mysql-service.default.svc.cluster.local- name: REDIS_HOSTvalue: redis-service.default.svc.cluster.local- name: SPRING_PROFILES_ACTIVEvalue: 'prodk8s'
spring:datasource:url: jdbc:mysql://${MYSQL_HOST}:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMTusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverredis:host: ${REDIS_HOST}database: 0port: 6379password:
- 首先需要将Redis、MySQL等暴露为Service,之后会注册到DNS中
当Deployment启动OK后,暴露为服务:
kind: ServiceapiVersion: v1metadata:name: capsule-demo-app-servicespec:type: NodePortselector:app: capsule-demo-appports:- protocol: TCPport: 8080targetPort: 8080nodePort: 32141
启动后,可以通过minikube service capsule-demo-app --url来拿到地址,也可以通过’kubectl cluster-info’拿到集群IP,然后端口号为NodePort(32141),这样就可以在宿主机上进行访问了服务。
