springboot2 + grpc + k8s + istio


項目情況說明:

ubuntu - 16.04

java - openjdk:8

springboot - 2.2.2.RELEASE

mysql - 5.7

mongodb - 4.0.14

redis - 3.0.6

grpc - grpc-spring-boot-starter - 2.6.1.RELEASE

項目目錄:

mypro - 

    device - 調用數據庫,grpc客戶端

    lib - 公共庫

    person - 調用數據庫,grpc服務端

項目啟動:

步驟一:項目建立

之所以從這里開始,因為我之前糾結過多模塊項目的建立...

IDE我使用的是IDEA

1.1、建立主目錄mypro

新建文件夾mypro,在IDEA中依次選擇:file -- New -- Project,彈出框選擇Spring Initializr,點擊Next。

注意紅框區域,選擇Maven POM,其它可以自己填

 

又彈出一個框來,什么也不選,直接Next

選擇地址(剛才新建的mypro文件夾)

至此,新建主目錄結束

1.2、建立子目錄

右鍵單擊主目錄mypro,選擇New,選擇Module

選擇Spring Initializr,直接Next

注意這里紅框選擇了Maven Project,其他可以自己填,填完Next

便於測試,這里選擇了Spring Web,點擊Next,彈出框直接Next就創建成功了

至此,第一個子模塊建立成功,按照上面的步驟依次建立其它子模塊,我這里建立了lib和person。

1.3、調整pom配置

主模塊:去掉依賴,引入子模塊,注意<packaging>pom</packaging>打包時會用到

 

簡單處理,把所有的依賴都由lib公共庫引入,device和person模塊引入lib公共庫

首先修改lib模塊:<parent>使用了主模塊的內容,直接復制過來即可,注意<packaging>jar</packaging>

修改device模塊:<parent>使用了主模塊的內容,直接復制過來即可,注意<packaging>war</packaging>

引入lib

peron模塊同上。

至此,項目建立完成。

步驟二:項目相關依賴

1.1、mysql

CREATE TABLE `user` (
  `name` char(16) NOT NULL,
  `age` int(11) DEFAULT NULL,
  `password` char(64) NOT NULL,
  `id` char(64) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
device | CREATE TABLE `device` (
  `id` char(64) NOT NULL,
  `name` char(16) NOT NULL,
  `code` char(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

使用的是mybatis-plus和代碼生成器

     <dependency>
           <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.0</version>
        </dependency>
    
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.1</version>
        </dependency>

代碼生成器:(我放在test中,加注釋的改成自己的就可以)

import org.springframework.boot.test.context.SpringBootTest;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.jupiter.api.Test;

@SpringBootTest
public class CodeGenerator {

    @Test
    public void generateCode() {
        String packageName = "com.sam.person";  // 當前的包
        boolean serviceNameStartWithI = true;
        generateByTables(serviceNameStartWithI, packageName);
    }

    private void generateByTables(boolean serviceNameStartWithI, String packageName) {
        GlobalConfig config = new GlobalConfig();
// 改成自己的 String dbUrl
= "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=UTC"; DataSourceConfig dataSourceConfig = new DataSourceConfig();
// 用戶名和密碼 dataSourceConfig.setDbType(DbType.MYSQL).setUrl(dbUrl).setUsername(
"root").setPassword("123456").setDriverName("com.mysql.cj.jdbc.Driver"); StrategyConfig strategyConfig = new StrategyConfig(); strategyConfig.setCapitalMode(true).setEntityLombokModel(false) .setNaming(NamingStrategy.underline_to_camel)
      .setInclude(
"user"); // 對應數據庫表名,表要先創建 config.setActiveRecord(false).setAuthor("sam") // 作者 .setOutputDir("/home/sam/mypro/person/src/main/java") // 路徑,到/java就可以 .setFileOverride(true) .setEnableCache(false); if (!serviceNameStartWithI) { config.setServiceName("%sService"); } new AutoGenerator() .setGlobalConfig(config) .setDataSource(dataSourceConfig) .setStrategy(strategyConfig) .setPackageInfo(new PackageConfig() .setParent(packageName) .setController("controller") .setEntity("entity.Do")) .execute(); } }

不熟悉test的,可以放在如圖這里,注意person有一個,device也要有一個,包和路徑內容要調整下。

生成的結構與下圖類似:

注意給啟動類上加MapperScan: @MapperScan("com.sam.device.mapper"),person同理。

沒有config,是后加的,application.yml見后。

開放遠程連接:(重要)

https://www.cnblogs.com/zhangkaiqiang/p/6646488.html

1.2、mongodb

可參考我另一篇隨筆

https://www.cnblogs.com/SamNicole1809/p/12097182.html

開放遠程連接:(重要)(參考,主要是需要設置bind-ip: 0.0.0.0,並重啟服務)

https://www.cnblogs.com/jinxiao-pu/p/7121307.html

1.3、redis

可參考我另一篇隨筆

https://www.cnblogs.com/SamNicole1809/p/12097440.html

開放遠程連接:(重要)

https://blog.csdn.net/mr_oldcold/article/details/81026928

1.4、yml配置

黃色標注的根據自己情況填,沒有可以不填,host填主機的Ip,不是localhost或127.0.0.1,部署時要用,這也是為什么要把所有數據庫的遠程連接都打開的原因。

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://host:3306/test?useUnicode=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=UTC
  redis:
    host: host
    password: 123456
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        max-wait: -1ms
        min-idle: 0
  data:
    mongodb:
      uri: mongodb://sam:123456@host:27017/test

至此,數據庫部分結束。

1.5、grpc

請參考我另一篇隨筆:(需要注意的是,device的yml在部署時需要修改address中的內容

https://www.cnblogs.com/SamNicole1809/p/12201227.html

1.6、測試controller

device的:

import com.sam.device.entity.Do.Device;
import com.sam.device.service.grpc.DeviceGrpcService;
import com.sam.device.service.impl.DeviceServiceImpl;
import com.sam.lib.utils.MongoUtils;
import com.sam.lib.utils.RedisUtils;
import com.sam.lib.utils.StringUtils;
import com.sam.lib.utils.TestUtils;
import org.bson.Document;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author sam
 * @since 2020-01-13
 */
@RestController
@RequestMapping("/device")
public class DeviceController {

    private final TestUtils testUtils;
    private final DeviceServiceImpl deviceService;
    private final RedisUtils redisUtils;
    private final MongoUtils mongoUtils;
    private final DeviceGrpcService deviceGrpcService;

    public DeviceController(TestUtils testUtils, DeviceServiceImpl deviceService, RedisUtils redisUtils, MongoUtils mongoUtils, DeviceGrpcService deviceGrpcService) {
        this.testUtils = testUtils;
        this.deviceService = deviceService;
        this.redisUtils = redisUtils;
        this.mongoUtils = mongoUtils;
        this.deviceGrpcService = deviceGrpcService;
    }

    @GetMapping("/get")
    public String getDevice() {
        return StringUtils.getResult(testUtils.setLabel("Device"));
    }

    @GetMapping("/mysql")
    public String setMysql() {
        Device device = new Device();
        device.setId(UUID.randomUUID().toString());
        device.setCode("123456");
        device.setName("device-dev");
        boolean flag = deviceService.save(device);
        if (flag) {
            return device.toString();
        }
        return "Save device error";
    }

    @GetMapping("/redis")
    public String setRedis() {
        redisUtils.vSet("name", "device");
        String name = redisUtils.vGet("name").toString();
        return "Redis write success, name is " + name;
    }

    @GetMapping("/mongo")
    public String setMongo() {
        Document doc = new Document();
        String mid = UUID.randomUUID().toString();
        doc.put("mid", mid);
        doc.put("name", "device");
        mongoUtils.insertOne("device", doc);
        Document query = new Document();
        query.put("mid", mid);
        Document result = mongoUtils.findOne("device", query);
        return "Mongo write success, result is " + result.toString();
    }

    @GetMapping("/grpc")
    public String setGrpc() {
        String result = deviceGrpcService.getUser("device");
        if ("".equals(result)) {
            return "Result is blank";
        }
        return result;
    }
}

person的:

import com.sam.lib.utils.MongoUtils;
import com.sam.lib.utils.RedisUtils;
import com.sam.lib.utils.StringUtils;
import com.sam.lib.utils.TestUtils;
import com.sam.person.entity.Do.User;
import com.sam.person.service.impl.UserServiceImpl;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author sam
 * @since 2020-01-13
 */
@RestController
@RequestMapping("/user")
public class UserController {

    private final TestUtils testUtils;
    private final UserServiceImpl userService;
    private final RedisUtils redisUtils;
    private final MongoUtils mongoUtils;

    public UserController(TestUtils testUtils, UserServiceImpl userService, RedisUtils redisUtils, MongoUtils mongoUtils) {
        this.testUtils = testUtils;
        this.userService = userService;
        this.redisUtils = redisUtils;
        this.mongoUtils = mongoUtils;
    }

    @GetMapping("/get")
    public String getUser() {
        return StringUtils.getResult(testUtils.setLabel("User"));
    }

    @GetMapping("/mysql")
    public String setMysql() {
        User user = new User();
        user.setId(UUID.randomUUID().toString());
        user.setName("sam");
        user.setAge(31);
        user.setPassword("123456");
        boolean flag = userService.save(user);
        if (flag) {
            return user.toString();
        }
        return "Save user error";
    }

    @GetMapping("/redis")
    public String setRedis() {
        redisUtils.vSet("name", "person");
        String name = redisUtils.vGet("name").toString();
        return "Redis write success, name is " + name;
    }

    @GetMapping("/mongo")
    public String setMongo() {
        Document doc = new Document();
        String mid = UUID.randomUUID().toString();
        doc.put("mid", mid);
        doc.put("name", "device");
        mongoUtils.insertOne("device", doc);
        Document query = new Document();
        query.put("mid", mid);
        Document result = mongoUtils.findOne("device", query);
        return "Mongo write success, result is " + result.toString();
    }
}

至此,依賴部分結束。

步驟三:搭建k8s和istio環境

請參考我另一篇隨筆:

https://www.cnblogs.com/SamNicole1809/p/12172887.html

步驟四:項目打包

1.1、跳過測試

在所有子模塊的pom文件中增加:<skipTests>true</skipTests>

    <properties>
        <java.version>1.8</java.version>
        <skipTests>true</skipTests>
    </properties>

1.2、修改lib的pom

注釋掉原有的用於build,使用下面的build

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <classifier>exec</classifier>
                </configuration>
            </plugin>
        </plugins>
    </build>

1.3、打包

使用maven,一次執行mypro下的Lifecycle中的clean和package,會在person和device的target目錄下生成war包,並自動處理關於lib的依賴問題。

至此,項目打包完畢。

步驟五:部署

請確保已經參照了k8s和Istio的隨筆,環境搭建成功,並能夠正常運行,namespace: default開啟了istio自動注入功能。

可參考官方文檔:

https://preliminary.istio.io/zh/docs/examples/bookinfo/

namespace: kube-system 

namespace: istio-system

1.1、生成docker鏡像

創建文件夾doc,將生成的war包復制到doc文件夾下,並創建文件Dockerfile,沒有后綴,注意文件名必須這樣

Dockerfile文件內容:

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG device
COPY device.war app.war
ENTRYPOINT ["java","-Djava.security.egdod=file:/dev/./urandom","-jar","/app.war"]

這里我把war包改名為device.war了,注意一下。

person同理,將里面的device改為person就好。

生成鏡像命令:

名稱(黃色標記)建議不要更改,因為yaml文件會用到,以后可以自己改。

docker build -t device-service .
docker build -t person-service .

1.2、添加yaml文件

網關:mypro-gateway.yaml

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: mypro-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: mypro
spec:
  hosts:
    - "*"
  gateways:
    - mypro-gateway
  http:
    - match:
        - uri:
            prefix: /user
      route:
        - destination:
            host: person
            port:
              number: 8080
    - match:
        - uri:
            prefix: /device
      route:
        - destination:
            host: device
            port:
              number: 8080

deployment和service:mypro.yaml

##################################################################################################
# person service
##################################################################################################
apiVersion: v1
kind: Service
metadata:
  name: person
  labels:
    app: person
    service: person
spec:
  ports:
    - name: http
      port: 8080
    - name: grpc
      port: 9898
      protocol: TCP
  selector:
    app: person
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: mypro-person
  labels:
    account: person
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: person-v1
  labels:
    app: person
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: person
      version: v1
  template:
    metadata:
      labels:
        app: person
        version: v1
    spec:
      serviceAccountName: mypro-person
      containers:
        - name: person
          image: person-service:latest
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
              name: http
            - containerPort: 9898
              name: grpc
              protocol: TCP
---
##################################################################################################
# device service
##################################################################################################
apiVersion: v1
kind: Service
metadata:
  name: device
  labels:
    app: device
    service: device
spec:
  ports:
    - name: http
      port: 8080
    - name: grpc
      port: 9898
      protocol: TCP
  selector:
    app: device
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: mypro-device
  labels:
    account: device
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: device-v1
  labels:
    app: device
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: device
      version: v1
  template:
    metadata:
      labels:
        app: device
        version: v1
    spec:
      serviceAccountName: mypro-device
      containers:
        - name: device
          image: device-service:latest
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
              name: http
            - containerPort: 9898
              name: grpc
              protocol: TCP
---

說明一下:

1、9898是grpc端口,8080是person和device的默認端口

2、device中的yml文件需要把address修改如下:(對應的服務名)

address: 'static://person:9898'

1.3、部署

到yaml相關文件目錄下執行如下命令:

安裝網關:

kubectl apply -f mypro-gateway.yaml

查看:

設置url:(參考官方文檔)

https://preliminary.istio.io/zh/docs/tasks/traffic-management/ingress/ingress-control/#determining-the-ingress-i-p-and-ports

echo GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT

顯示你能夠訪問的url地址,通過這個地址可以訪問項目,正常是host:port都有。

部署服務:

kubectl apply -f mypro.yaml

查看:

至此項目部署成功。

步驟五:測試

http://host:port/device/get

http://host:port/device/mysql 

http://host:port/device/mongo

http://host:port/device/redis

http://host:port/device/grpc

person相同

至此,測試成功。

結語:

我想說的是:

網絡的目的在於分享,這個項目是在別人的經驗上搭建起來的,我只是做了一個整合。

本文的目的在於幫助剛接觸k8s和istio的新人,少采坑。

希望用微服務的人越來越多,把越來越多的資料經驗分享出來。

我是一個新手,希望文中有錯誤的大家請批評指正,以免影響更多的人。

歡迎多多交流。

Git地址:

https://github.com/SamNicole1809/mypro


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM