使用kubernetes部署云服务

这个小项目通过kubernetes部署,包括eureka服务器,userService,adminService,并部署mysql服务存储持久化对象

  1. 加入各类组件并实现基本功能

    以user-service的pom为例,加入eureka、mysql、springcloud各类组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.1.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>io.daocloud</groupId>
    <artifactId>user-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>user-service</name>
    <description>Demo project for Spring Boot</description>

    <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR6</spring-cloud.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
    <exclusion>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    </dependencies>

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>

    <build>
    <finalName>user-service</finalName>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    </project>

    编码实现基本功能,以添加用户为例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package io.daocloud.userservice.service;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import io.daocloud.userservice.dao.UserDao;
    import io.daocloud.userservice.domain.User;

    /**
    * Author: Garroshh date: 2020/7/9 8:19 下午
    */
    @Service
    public class UserService {
    @Autowired
    private UserDao userDao;

    public User add(User user) {
    return userDao.save(user);
    }

    public User get(long id) {
    return userDao.getOne(id);
    }
    }

  2. 启动mysql服务

    • 拉取镜像
    1
    2
    docker pull mysql:8.0.33
    //这个版本要与deployment.yaml和pv.yaml里面的版本一致
    • 部署mysql
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    piVersion: v1
    kind: Service
    metadata:
    name: mysql
    spec:
    ports:
    - port: 3306
    selector:
    app: mysql
    clusterIP: None
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: mysql
    spec:
    selector:
    matchLabels:
    app: mysql
    strategy:
    type: Recreate
    template:
    metadata:
    labels:
    app: mysql
    spec:
    containers:
    - image: mysql:8.0.33
    name: mysql
    env:
    # 在实际中使用 secret
    - name: MYSQL_ROOT_PASSWORD
    value: dangerous
    ports:
    - containerPort: 3306
    name: mysql
    volumeMounts:
    - name: mysql-persistent-storage
    mountPath: /var/lib/mysql
    volumes:
    - name: mysql-persistent-storage
    persistentVolumeClaim:
    claimName: mysql-pv-claim

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: mysql-pv-volume
    labels:
    type: local
    spec:
    storageClassName: manual
    capacity:
    storage: 100Mi
    accessModes:
    - ReadWriteOnce
    hostPath:
    # 注意这里改成服务器上的一个空文件夹路径
    path: "/Users/lyl/moss"
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: mysql-pv-claim
    spec:
    storageClassName: manual
    accessModes:
    - ReadWriteOnce
    resources:
    requests:
    storage: 100Mi
    • apply
    1
    2
    kubectl apply -f mysql-deployment.yaml  
    kubectl apply -f mysql-pv.yaml
    • 在容器中创建user database
    1
    2
    3
    4
    5
    6
    7
    8
    9
    kubectl run -it --rm --image=mysql:8.0.33 --restart=Never mysqlclient -- mysql -h mysql -pdangerous
    If you don't see a command prompt, try pressing enter.

    mysql> create database user;
    Query OK, 1 row affected (0.03 sec)

    mysql> ^C
    mysql> exit
    Bye
    • 查看资源
    1
    2
    3
    4
    5
    6
    7
    kubectl get po,svc                                                                                 
    NAME READY STATUS RESTARTS AGE
    pod/mysql-7b99cd9bc9-js7lh 1/1 Running 0 23m

    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 176m
    service/mysql ClusterIP None <none> 3306/TCP 23m
    • 这里可能会遇到一个问题:The PersistentVolume “mysql-pv-volume” is invalid: spec.persistentvolumesource: Forbidden: spec.persistentvolumesource is immutable after creation,这是因为这个mysql服务已经存在了,解决方案是换一个名字或者把原来服务的删除
    1
    2
    3
    4
    kubectl get pv
    kubectl get pvc
    kubectl delete pvc mysql-pv-claim
    kubectl delete pv mysql-pv-volume
  3. 接下来将三个服务的jar包打好

    在三个服务的子文件夹下运行

    1
    mvn -B -Dmaven.test.skip clean package
  4. 接下来根据dockerfile将容器build出来

    特别注意这里的标签名要和development.yaml里面的名字相符

    1
    2
    3
    docker build -t user-service:2023 .
    docker build -t admin-service:2023 .
    docker build -t eureka:2023 .
  5. 接下来我们apply,通过kubernetes部署三个service

    1
    2
    3
    kubectl apply -f admin-deployment.yaml 
    kubectl apply -f admin-service.yaml
    //user-service和eureka以此类推

    现在我们查看资源:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    kubectl get po,svc
    NAME READY STATUS RESTARTS AGE
    pod/admin-service-84444789db-mtrdt 1/1 Running 0 7m20s
    pod/eurkea-76cff4d569-j2hbt 1/1 Running 0 9m41s
    pod/mysql-7b99cd9bc9-js7lh 1/1 Running 0 55m
    pod/user-service-7cb8755bd7-xhd6w 1/1 Running 0 9m2s

    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    service/admin-service NodePort 10.108.205.242 <none> 10000:30521/TCP 7m7s
    service/eureka NodePort 10.104.237.255 <none> 8080:31011/TCP 9m28s
    service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3h27m
    service/mysql ClusterIP None <none> 3306/TCP 55m
    service/user-service NodePort 10.99.98.6 <none> 9090:32657/TCP 8m50s
  6. 验证部署已经成功

    向user-service发送一个post请求

    1
    curl -H "Content-Type: application/json" -X POST -d '{"name":"张三", "pwd":"888"}'  http://localhost:32657/user 

    收到回应

    1
    {"id":1,"name":"张三","pwd":"888"}

    向user-service发送错误的post请求,收到报错

    同时也可以访问eureka服务

    访问特定的id

    在admin-service中编码

    1
    2
    3
    4
    @GetMapping("/user")
    public Object get(@RequestBody @Valid UserDto id){
    return userService.get(id);
    }

    发送请求

    1
    curl http://localhost:30521/user?id=1

    收到回应

    1
    {"id":1,"name":"张三"}
  7. 负载均衡

    直接使用自带服务中的RandomRule服务

    在admin_service中的UserFeign.java中设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package io.daocloud.adminservice.fegin;

    import com.netflix.loadbalancer.RandomRule;
    import io.daocloud.adminservice.dto.UserDto;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;

    /**
    * Author: Garroshh
    * date: 2020/7/9 8:39 下午
    */
    @FeignClient(name = "user-service", configuration = RandomRule.class) //使用随机策略
    public interface UserFeign {

    @PostMapping("/user")
    Object add(UserDto userDto);

    @GetMapping("/port")
    String port();
    }

    接下来我们启动两个user-service服务,分别注册端口9090和9000

    application.properties

    1
    2
    spring.application.name=user-service
    server.port=9090 //另一个就是9000

    更改user_deployment和user_service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: user-service-1 //另一个就是-2
    labels:
    app: user-service-1
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: user-service-1
    template:
    metadata:
    labels:
    app: user-service-1
    spec:
    hostname: user-service-1
    containers:
    - name: user-service-1
    image: user-service-1:2023
    imagePullPolicy: IfNotPresent
    env:
    - name: EUREKA_URL
    value: http://eureka:8080/eureka
    ports:
    - containerPort: 8080
    resources:
    requests:
    cpu: 0.5
    memory: 256Mi
    limits:
    cpu: 0.5
    memory: 256Mi
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    apiVersion: v1
    kind: Service
    metadata:
    name: user-service-1 //另一个就是-2
    labels:
    app: user-service-1
    spec:
    type: NodePort
    ports:
    - port: 9090
    targetPort: 9090
    selector:
    app: user-service

    重新运行之后就可以看到新的服务在eureka上注册了

    这时访问admin-service的port地址,就可以发现相应的user-service是随机的