Google Code Prettify

2021年2月18日 星期四

Spring Cloud Gateway: getting started

Spring Cloud 在 2020 年用 Gateway 取代原本的網關 Zuul,新的網關 Gateway 功能更強,效率更高,對 developer 來說,使用上是一樣的簡單。


如上圖是我的環境,我的所有服務放在一台 CentOS 7 上的 Docker 裡,Docker 上有服務註冊的 consul,有提供服務的 wealth-system,這個服務當輸入 http://192.168.50.13:8080/customer/E243350588 會傳回客戶的基本資料。現在要來看 gateway 怎麼寫 …

** 建立一個 spring boot 專案 **

         在 build.gradle 的 dependencies 中加入以下內容,以導入所需要的 jar 檔。

  1. implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
  2. implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
  3. implementation 'org.springframework.boot:spring-boot-starter-actuator'

  • 第 1 行是 consul 註冊、查詢服務所需要的 jar 檔
  • 第 2 行是導入新版 gateway
  • 第 3 行是 health check 所需要的 jar 檔

  1. server:
  2. servlet:
  3. context-path: /
  4. port: 80
  5. management:
  6. server:
  7. base-path: /
  8. port: 5001
  9. endpoint:
  10. health:
  11. probes:
  12. enabled: true
  13. show-details: always
  14. spring:
  15. application:
  16. name: fstop-gateway
  17. profiles:
  18. active: ${SPRING_PROFILES_ACTIVE}
  19. main:
  20. banner-mode: off
  21. cloud:
  22. consul:
  23. enabled: true
  24. host: 192.168.50.13
  25. port: 8500
  26. discovery:
  27. prefer-ip-address: true
  28. instance-id: ${spring.application.name}:${random.value}
  29. register-health-check: true
  30. health-check-path: /actuator/health
  31. health-check-interval: 30s
  32. tags: ${spring.application.name}
  33. gateway:
  34. routes:
  35. - id: wealth
  36. uri: lb://wealth-system
  37. predicates:
  38. - Path=/customer/**

上面是 application.yml 的內容,說明如下:

  • 第 24~34 行,將 gateway 註冊到 consul,如果不註冊到 consul,就無法使用 consul 的服務查詢功能。
  • 第 35~40 行是 gateway 的設定,第 37 行只是個標識 (命名),取個有意義的名稱即可,第 38 行指出,當網址有 /customer/** 時,導向 wealth-system 這個服務。特別注意,這裡寫的是 lb://wealth-system (lb: load balance),就是要使用 consul 提供的服務發現的方式導向,也可以寫成 http://192.168.50.8080,這樣就是直接導向服務所在的網址。

2021年2月16日 星期二

移除 VirtualBox 產生的「網路連線」介面卡

 使用 vagrant 創建 VM 非常方便,每次創建,vagrant 會透過 VirutalBox 在「網路連線」中,新增一個介面卡。

如果 VM 不想要了,透過 vagrant destroy 指令,可以移除這些 VM 使用的介面卡。但是,萬一"忘了"使用上述指令移除呢? 「網路連線」中日積月累的,會產生不少垃圾,這時候可以使用以下指令移除:

  1. vboxmanage hostonlyif remove "VirtualBox Host-Only Ethernet Adapter #2"
後面雙引號中的字串,要和要移除的「裝置名稱」完全一致。

2021年2月15日 星期一

consul: getting started

2020 年底,Spring Cloud 正式以 consul 取代 Eureka,成為 Spring Cloud 微服務架構中,服務註冊、查詢的元件。關於 consul 和 Eureka 的優劣,可以透過 google 得到不少訊息,在這裡就不多說,這裡整理的,主要是從程式開發的角度看, 當改用 consul,要怎麼開發、測試?

Spring Cloud 要部署的元件不少,我把它們部署在 Windows 10 Docker Desktop 裡的 Kubernetes 上,下面整理的資料會有一些 K8s 部署需要的 yaml 檔。

1. 部署 consul

為了讓 Kubernetes 外可以 consul 存取,要有一個 service,內容如下:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: consul-svc
  5. labels:
  6. app: consul
  7. spec:
  8. type: LoadBalancer
  9. ports:
  10. - name: port8301
  11. protocol: TCP
  12. port: 8301
  13. targetPort: 8301
  14. - name: port8302
  15. protocol: TCP
  16. port: 8302
  17. targetPort: 8302
  18. - name: port8300
  19. protocol: TCP
  20. port: 8300
  21. targetPort: 8300
  22. - name: ui
  23. port: 8500
  24. targetPort: 8500
  25. - name: port8600
  26. protocol: TCP
  27. port: 8600
  28. targetPort: 8600
  29. selector:
  30. app: consul

因為是 Docker Desktop 單節點的 Kubernetes,EXTERNAL-IP 會是 localhost,從本機透過 8500 即可存取。接著要部署 consul,官網建議部署 consul 最好是 3 個或 5 個成為 cluster,這裡單純是要測試用,只部署一個,因為是成為各服務註冊的元件,部署時選擇 StatefulSet,yaml 檔如下。

  1. apiVersion: apps/v1
  2. kind: StatefulSet
  3. metadata:
  4. name: consul
  5. spec:
  6. serviceName: consul-svc
  7. podManagementPolicy: "Parallel"
  8. replicas: 1
  9. template:
  10. metadata:
  11. labels:
  12. app: consul
  13. spec:
  14. terminationGracePeriodSeconds: 10
  15. containers:
  16. - name: consul
  17. image: consul:latest
  18. args:
  19. - "agent"
  20. - "-server"
  21. - "-bootstrap-expect=1"
  22. - "-ui"
  23. - "-data-dir=/consul/data"
  24. - "-bind=0.0.0.0"
  25. - "-client=0.0.0.0"
  26. - "-advertise=$(PODIP)"
  27. env:
  28. - name: PODIP
  29. valueFrom:
  30. fieldRef:
  31. fieldPath: status.podIP
  32. - name: NAMESPACE
  33. valueFrom:
  34. fieldRef:
  35. fieldPath: metadata.namespace
  36. ports:
  37. - containerPort: 8500
  38. name: ui-port
  39. - containerPort: 8400
  40. name: alt-port
  41. - containerPort: 53
  42. name: udp-port
  43. - containerPort: 8443
  44. name: https-port
  45. - containerPort: 8080
  46. name: http-port
  47. - containerPort: 8301
  48. name: serflan
  49. - containerPort: 8302
  50. name: serfwan
  51. - containerPort: 8600
  52. name: consuldns
  53. - containerPort: 8300
  54. name: server
  55. selector:
  56. matchLabels:
  57. app: consul

部署好之後,以 kubectl get statefulset 檢查看看有沒有成功,應該可以看到如下的結果。



打開瀏覽器輸入 http://localhost:8500,應該可以看到如下內容,consul 正常運作。


2. 註冊一個服務到 consul

假設有個財富管理系統,命名為 wealth-system,裡面提供了一個服務 /customer/{idno},可以透過身分證字號查詢客戶基本資料,要如何註冊到 consul ? 首先在 build.gradle 中引入相關的 jar 檔,在 dependencies 中加入如下內容:

  1. implementation 'org.springframework.boot:spring-boot-starter-actuator'
  2. implementation 'org.springframework.cloud:spring-cloud-starter-config'
  3. implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
  4. implementation 'org.springframework.cloud:spring-cloud-starter-consul-config'

接著在 applicatoin.yml 中設定如下內容:

  1. management:
  2. server:
  3. base-path: /
  4. port: 5001
  5. endpoint:
  6. health:
  7. probes:
  8. enabled: true
  9. show-details: always
  10.  
  11. spring:
  12. application:
  13. name: wealth-system
  14. profiles:
  15. active: ${SPRING_PROFILES_ACTIVE}
  16. main:
  17. banner-mode: off
  18. cloud:
  19. consul:
  20. enabled: true
  21. host: consul-svc
  22. port: 8500
  23. discovery:
  24. prefer-ip-address: true
  25. instance-id: ${spring.application.name}:${random.value}
  26. service-name: ${spring.application.name}
  27. register-health-check: true
  28. health-check-path: /actuator/health/liveness
  29. health-check-interval: 60s

  • 第 1 ~ 9 行,設定 management,這是設定監控工具 Actuator 傾聽 5001 port,其中第 6 ~ 8 行指出要提供 health check 所需要的探針, 第 28 行即是指出探針的網址。
  • 同一個服務部署到 consul,會有好幾個 instance,以避免一個 instance 掛了,就無法提供服務,所以第 25 行指出 instance 的名稱,並且後面以亂數來表示,這樣每個 instance 的 intance id 自然就會不同。
  • 第 21、22 行,指出 consul 所在的 IP 和 port。

程式中怎麼啟用 consul? 在 @SpringBootApplication 所在的 class 上加入 @EnableDiscoveryClient。

Controller 程式這裡就不多做說明,基本上就是要提供一個網址為 /customer/{idno} 的服務,可以透過身分證字號查詢客戶基本資料。

compile 好之後,要 push 到 docker hub,方法如下:

(1) 製作 image

Dockerfile 內容如下:

  1. FROM openjdk:11
  2. ADD wealth-system.jar wealth-system.jar
  3. ENV SPRING_PROFILES_ACTIVE sit
  4. EXPOSE 80/tcp
  5. EXPOSE 5001/tcp
  6. ENTRYPOINT ["java","-jar","/wealth-system.jar"]

第 3 行傳入的參數,代表這是一個 sit 環境。第 4、5 行則是對外開放的 port。

在 Dockerfile 所在目錄下指令將 image push 到 docker hub,我在 docker hub 有一個帳號是 twleader 所以第 1 行產生 image 時指定的 image 名稱前有 twleader/。
  1. docker build -t twleader/wealth-system .
  2. docker push

(2) 部署 image 到 Kubernetes

consul 需要固定的 IP,採用 StatefulSet 方式部署,一般的服務每次產生時 IP 可以不一樣,採用 Deployment 方式部署,用這種方式部署會先產生 ReplicaSet 再產 Pod,yaml 檔內容如下:
  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: wealth
  5. spec:
  6. replicas: 2
  7. selector:
  8. matchLabels:
  9. app: wealth
  10. template:
  11. metadata:
  12. name: wealth
  13. labels:
  14. app: wealth
  15. spec:
  16. containers:
  17. - image: twleader/wealth-system:latest
  18. name: wealth
  19. ports:
  20. - containerPort: 80
  21. name: api-port
  22. - containerPort: 5001
  23. name: management-port
  24. env:
  25. - name: SPRING_PROFILES_ACTIVE
  26. value: "sit"
  27. - name: POD_IP
  28. valueFrom:
  29. fieldRef:
  30. fieldPath: status.podIP
  31. volumeMounts:
  32. - name: my-pvc
  33. mountPath: /mnt/logs
  34. volumes:
  35. - name: my-pvc
  36. persistentVolumeClaim:
  37. claimName: my-pvc
  • 第 6 行指出會產生兩個 Pod
  • 第 31~37 行,這支程式產生的 log 會放在 /mnt/logs 目錄下,透過這樣的設定,可以讓 log 不會因為 Pod 消失而不見 (會儲存到 PV 所指定的地方)。
部署之後,應該可以在 consul ui 看到如下的內容,一開始可能會是紅色的,需過一段時間,讓 consul 透過 health check 確認服務已經 ready,才會轉為綠色。

3. client

上面已經將服務註冊到 consul 了,那麼其它程式或服務要怎麼存取這些服務呢?

(1) build.gradle

在 dependencies 中加入如下的依賴:
  1. implementation 'org.springframework.boot:spring-boot-starter-actuator'
  2. implementation 'org.springframework.cloud:spring-cloud-starter'
  3. implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'

(2) application.yml

  1. management:
  2. server:
  3. base-path: /
  4. port: 15001
  5. endpoint:
  6. health:
  7. probes:
  8. enabled: true
  9. show-details: always
  10.  
  11. spring:
  12. application:
  13. name: consul-client
  14. profiles:
  15. active: ${SPRING_PROFILES_ACTIVE}
  16. main:
  17. banner-mode: off
  18. cloud:
  19. consul:
  20. enabled: true
  21. host: consul-svc
  22. port: 8500
  23. discovery:
  24. instance-id: ${spring.application.name}:${random.value}
  25. service-name: ${spring.application.name}
  26. register-health-check: true
  27. health-check-path: /actuator/health/liveness
  28. health-check-interval: 60s

(3) RestTemplate

  1. @Configuration
  2. public class RestConfig {
  3. @Bean
  4. @LoadBalanced
  5. public RestTemplate restTemplate() {
  6. return new RestTemplate();
  7. }
  8. }
第 4 行的 @LoadBalanced 很重要! 這樣子才可以透過 consul 上註冊的服務名稱存取到服務。

(4) Controller

  1. @RestController
  2. @RequestMapping("/wealth")
  3. @Slf4j
  4. public class WealthController {
  5. @Autowired
  6. RestTemplate rest;
  7. @GetMapping("/customer/{idno}")
  8. public ResponseEntity<?> findCustomerByIdno(@PathVariable String idno) {
  9. log.info("idno = " + idno);
  10. try {
  11. ResponseEntity<CustomerModel> response = rest.getForEntity("http://wealth-system/customer/" + idno, CustomerModel.class);
  12. CustomerModel customer = response.getBody();
  13. log.info(customer.toString());
  14. return new ResponseEntity<CustomerModel>(customer, HttpStatus.OK);
  15. }
  16. catch (Exception e) {
  17. log.error(e.getMessage(), e);
  18. return new ResponseEntity<Exception>(e, HttpStatus.INTERNAL_SERVER_ERROR);
  19. }
  20. }
  21. }
  • 看第 5、6 行,RestTemplate 不能直接 new,需要用 @Autowired (註)。 
  • 第 14 行,要對一個服務 request 時,不需要指出該服務的網址或IP,而是直接對服務名稱 wealth-system。

(5) 產生 image

Dockerfile 內容如下:
  1. FROM openjdk:11
  2. ADD consul-client.jar consul-client.jar
  3. ENV SPRING_PROFILES_ACTIVE sit
  4. EXPOSE 18080/tcp
  5. EXPOSE 15001/tcp
  6. ENTRYPOINT ["java","-jar","/consul-client.jar"]
於 Dockerfile 所在目錄下底下的指令:
  1. docker build -t twleader/consul-client .
  2. docker push twleader/consul-client

(6) 部署到 Kubernetes

先為 client 建立一個 service,方便測試,yaml 檔如下:
  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: consul-client-svc
  5. spec:
  6. type: LoadBalancer
  7. ports:
  8. - name: consul-client
  9. port: 18080
  10. targetPort: 18080
  11. - name: consul-client-management
  12. port: 15001
  13. targetPort: 15001
  14. selector:
  15. app: consul-client
接著當然要部署 POD, 使用如下的 deployment yaml file:
  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: consul-client
  5. spec:
  6. replicas: 2
  7. selector:
  8. matchLabels:
  9. app: consul-client
  10. template:
  11. metadata:
  12. name: consul-client
  13. labels:
  14. app: consul-client
  15. spec:
  16. containers:
  17. - image: twleader/consul-client:latest
  18. name: consul-client
  19. ports:
  20. - containerPort: 18080
  21. name: api-port
  22. - containerPort: 15001
  23. name: management-port
  24. env:
  25. - name: SPRING_PROFILES_ACTIVE
  26. value: "sit"
  27. - name: POD_IP
  28. valueFrom:
  29. fieldRef:
  30. fieldPath: status.podIP
  31. volumeMounts:
  32. - name: my-pvc
  33. mountPath: /mnt/logs
  34. volumes:
  35. - name: my-pvc
  36. persistentVolumeClaim:
  37. claimName: my-pvc

一切部署妥當,應該可以在 consul ui 中看到如下內容,這次要使用到的服務都已經在上面了!


打開瀏覽器,輸入 http://localhost:18080/wealth/customer/E243350588 可以得到傳回的 JSON format 資料如下:
  1. {"idno":"E243350588","name":"Nancy","phoneNo":"0909000911","email":"nancy@yahoo.com"}

2021年2月12日 星期五

install MS SQL Server@CentOS7

  1. Vagrant.configure("2") do |config|
  2. config.vm.box = "generic/centos7"
  3. config.vm.box_check_update = false
  4. config.vm.box_download_insecure = true
  5. config.vm.define "db-server" do |db|
  6. # db.vm.synced_folder "mssql", "/var/opt/mssql"
  7. db.vm.synced_folder "mssql", "/home/vagrant/mssql"
  8. db.vm.hostname = "db-server"
  9. db.vm.provider "virtualbox" do |vb|
  10. vb.gui = false
  11. vb.memory = 4096
  12. vb.cpus = 1
  13. end
  14. db.vm.network "private_network", ip: "192.168.50.13"
  15. end
  16. end
vagrant 是管理 VM 很方便的工具,先到官網下載並安裝後,建立一個內容如上檔名為 Vagrantfile 的設定檔,然後在該檔所在的路徑執行如下指令:
  1. vagrant up
  2. vagrant ssh 192.168.50.13
這樣會啟動一個 CentOS 7 的 VM,接著 ssh 進入該 VM, 底下將在 VM 裡安裝 MS SQL Server 2019。 

設定套件
  1. sudo curl -o /etc/yum.repos.d/mssql-server.repo https://packages.microsoft.com/config/rhel/7/mssql-server-2019.repo
# Error: Failed to download metadata for repo 'appstream': Cannot download repomd.xml: Cannot download repodata/repomd.xml: All mirrors were tried 
如果出現如上錯誤,編輯 /etc/resolv.conf,設定 nameserver 8.8.8.8,再安裝一次。

安裝 SQL Server 套件
  1. sudo yum install -y mssql-server.x86_64

安裝 SQL Server (選 2 Developer)
  1. sudo /opt/mssql/bin/mssql-conf setup
出現綠色 active (running) 表示 sql server 正常運行
  1. sudo systemctl status mssql-server.service

啟用 SQL Server Agent
  1. sudo /opt/mssql/bin/mssql-conf set sqlagent.enabled true
將 vagrant 加入 mssql 群組
  1. sudo usermod -aG mssql vagrant

重新啟動 SQL Server 服務
  1. sudo systemctl restart mssql-server

Confirm installation
  1. sudo rpm -qi mssql-server

開防火牆
  1. sudo firewall-cmd --add-port=1433/tcp --permanent
  2. sudo firewall-cmd --reload

安裝工具程式
  1. sudo curl -o /etc/yum.repos.d/msprod.repo https://packages.microsoft.com/config/rhel/7/prod.repo
  2. sudo yum install compat-openssl10

Install SQL Server command-line tools
  1. sudo yum -y install mssql-tools unixODBC-devel
設定開機後 sql server 自動啟動
  1. sudo systemctl is-enabled mssql-server.service

Add /opt/mssql/bin/ to your $PATH variable
  1. sudo echo 'export PATH=$PATH:/opt/mssql/bin:/opt/mssql-tools/bin' | sudo tee /etc/profile.d/mssql.sh
  2. source /etc/profile.d/mssql.sh
# 改變預設路徑 
# sudo /opt/mssql/bin/mssql-conf set filelocation.defaultdatadir /home/vagrant/mssql/data 
# sudo /opt/mssql/bin/mssql-conf set filelocation.defaultlogdir /home/vagrant/mssql/log 

重啟 sql server
  1. sudo systemctl restart mssql-server