雖然 IPv6 實施很多年了,在平常的應用上,多半還是使用 IPv4,以下是 IPv4 的內部 IP 範圍:
- 10.0.0.0 ~ 10.255.255.255
- 172.16.0.0 ~ 172.31.255.255
- 192.168.0.0 ~192.168.255.255
雖然 IPv6 實施很多年了,在平常的應用上,多半還是使用 IPv4,以下是 IPv4 的內部 IP 範圍:
今天安裝了 eclipse 2021-09 版,加了 lombok 時是在 eclipse.ini 中加入如下設定:
打開 eclipse,開啟 *.java 檔時,都會出現如下錯誤:
- -javaagent:C:\...\eclipse\lombok-1.18.20.jar
解決的辦法是在 eclipse.ini 中加入如下內容,因為這是 Java Module 引起的。
- Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @57c69937
- --add-exports=java.base/sun.nio.ch=ALL-UNNAMED
- --add-opens=java.base/java.lang=ALL-UNNAMED
- --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
- --add-opens=java.base/java.io=ALL-UNNAMED
- --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED
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 檔。
- implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
- implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
- implementation 'org.springframework.boot:spring-boot-starter-actuator'
- server:
- servlet:
- context-path: /
- port: 80
- management:
- server:
- base-path: /
- port: 5001
- endpoint:
- health:
- probes:
- enabled: true
- show-details: always
- spring:
- application:
- name: fstop-gateway
- profiles:
- active: ${SPRING_PROFILES_ACTIVE}
- main:
- banner-mode: off
- cloud:
- consul:
- enabled: true
- host: 192.168.50.13
- port: 8500
- discovery:
- prefer-ip-address: true
- instance-id: ${spring.application.name}:${random.value}
- register-health-check: true
- health-check-path: /actuator/health
- health-check-interval: 30s
- tags: ${spring.application.name}
- gateway:
- routes:
- - id: wealth
- uri: lb://wealth-system
- predicates:
- - Path=/customer/**
上面是 application.yml 的內容,說明如下:
使用 vagrant 創建 VM 非常方便,每次創建,vagrant 會透過 VirutalBox 在「網路連線」中,新增一個介面卡。
如果 VM 不想要了,透過 vagrant destroy 指令,可以移除這些 VM 使用的介面卡。但是,萬一"忘了"使用上述指令移除呢? 「網路連線」中日積月累的,會產生不少垃圾,這時候可以使用以下指令移除:
後面雙引號中的字串,要和要移除的「裝置名稱」完全一致。
- vboxmanage hostonlyif remove "VirtualBox Host-Only Ethernet Adapter #2"
2020 年底,Spring Cloud 正式以 consul 取代 Eureka,成為 Spring Cloud 微服務架構中,服務註冊、查詢的元件。關於 consul 和 Eureka 的優劣,可以透過 google 得到不少訊息,在這裡就不多說,這裡整理的,主要是從程式開發的角度看, 當改用 consul,要怎麼開發、測試?
Spring Cloud 要部署的元件不少,我把它們部署在 Windows 10 Docker Desktop 裡的 Kubernetes 上,下面整理的資料會有一些 K8s 部署需要的 yaml 檔。
為了讓 Kubernetes 外可以 consul 存取,要有一個 service,內容如下:
- apiVersion: v1
- kind: Service
- metadata:
- name: consul-svc
- labels:
- app: consul
- spec:
- type: LoadBalancer
- ports:
- - name: port8301
- protocol: TCP
- port: 8301
- targetPort: 8301
- - name: port8302
- protocol: TCP
- port: 8302
- targetPort: 8302
- - name: port8300
- protocol: TCP
- port: 8300
- targetPort: 8300
- - name: ui
- port: 8500
- targetPort: 8500
- - name: port8600
- protocol: TCP
- port: 8600
- targetPort: 8600
- selector:
- app: consul
因為是 Docker Desktop 單節點的 Kubernetes,EXTERNAL-IP 會是 localhost,從本機透過 8500 即可存取。接著要部署 consul,官網建議部署 consul 最好是 3 個或 5 個成為 cluster,這裡單純是要測試用,只部署一個,因為是成為各服務註冊的元件,部署時選擇 StatefulSet,yaml 檔如下。
- apiVersion: apps/v1
- kind: StatefulSet
- metadata:
- name: consul
- spec:
- serviceName: consul-svc
- podManagementPolicy: "Parallel"
- replicas: 1
- template:
- metadata:
- labels:
- app: consul
- spec:
- terminationGracePeriodSeconds: 10
- containers:
- - name: consul
- image: consul:latest
- args:
- - "agent"
- - "-server"
- - "-bootstrap-expect=1"
- - "-ui"
- - "-data-dir=/consul/data"
- - "-bind=0.0.0.0"
- - "-client=0.0.0.0"
- - "-advertise=$(PODIP)"
- env:
- - name: PODIP
- valueFrom:
- fieldRef:
- fieldPath: status.podIP
- - name: NAMESPACE
- valueFrom:
- fieldRef:
- fieldPath: metadata.namespace
- ports:
- - containerPort: 8500
- name: ui-port
- - containerPort: 8400
- name: alt-port
- - containerPort: 53
- name: udp-port
- - containerPort: 8443
- name: https-port
- - containerPort: 8080
- name: http-port
- - containerPort: 8301
- name: serflan
- - containerPort: 8302
- name: serfwan
- - containerPort: 8600
- name: consuldns
- - containerPort: 8300
- name: server
- selector:
- matchLabels:
- app: consul
部署好之後,以 kubectl get statefulset 檢查看看有沒有成功,應該可以看到如下的結果。
假設有個財富管理系統,命名為 wealth-system,裡面提供了一個服務 /customer/{idno},可以透過身分證字號查詢客戶基本資料,要如何註冊到 consul ? 首先在 build.gradle 中引入相關的 jar 檔,在 dependencies 中加入如下內容:
- implementation 'org.springframework.boot:spring-boot-starter-actuator'
- implementation 'org.springframework.cloud:spring-cloud-starter-config'
- implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
- implementation 'org.springframework.cloud:spring-cloud-starter-consul-config'
接著在 applicatoin.yml 中設定如下內容:
- management:
- server:
- base-path: /
- port: 5001
- endpoint:
- health:
- probes:
- enabled: true
- show-details: always
- spring:
- application:
- name: wealth-system
- profiles:
- active: ${SPRING_PROFILES_ACTIVE}
- main:
- banner-mode: off
- cloud:
- consul:
- enabled: true
- host: consul-svc
- port: 8500
- discovery:
- prefer-ip-address: true
- instance-id: ${spring.application.name}:${random.value}
- service-name: ${spring.application.name}
- register-health-check: true
- health-check-path: /actuator/health/liveness
- health-check-interval: 60s
程式中怎麼啟用 consul? 在 @SpringBootApplication 所在的 class 上加入 @EnableDiscoveryClient。
Controller 程式這裡就不多做說明,基本上就是要提供一個網址為 /customer/{idno} 的服務,可以透過身分證字號查詢客戶基本資料。
compile 好之後,要 push 到 docker hub,方法如下:
Dockerfile 內容如下:
- FROM openjdk:11
- ADD wealth-system.jar wealth-system.jar
- ENV SPRING_PROFILES_ACTIVE sit
- EXPOSE 80/tcp
- EXPOSE 5001/tcp
- ENTRYPOINT ["java","-jar","/wealth-system.jar"]
第 3 行傳入的參數,代表這是一個 sit 環境。第 4、5 行則是對外開放的 port。
在 Dockerfile 所在目錄下指令將 image push 到 docker hub,我在 docker hub 有一個帳號是 twleader 所以第 1 行產生 image 時指定的 image 名稱前有 twleader/。
- docker build -t twleader/wealth-system .
- docker push
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: wealth
- spec:
- replicas: 2
- selector:
- matchLabels:
- app: wealth
- template:
- metadata:
- name: wealth
- labels:
- app: wealth
- spec:
- containers:
- - image: twleader/wealth-system:latest
- name: wealth
- ports:
- - containerPort: 80
- name: api-port
- - containerPort: 5001
- name: management-port
- env:
- - name: SPRING_PROFILES_ACTIVE
- value: "sit"
- - name: POD_IP
- valueFrom:
- fieldRef:
- fieldPath: status.podIP
- volumeMounts:
- - name: my-pvc
- mountPath: /mnt/logs
- volumes:
- - name: my-pvc
- persistentVolumeClaim:
- claimName: my-pvc
- implementation 'org.springframework.boot:spring-boot-starter-actuator'
- implementation 'org.springframework.cloud:spring-cloud-starter'
- implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
- management:
- server:
- base-path: /
- port: 15001
- endpoint:
- health:
- probes:
- enabled: true
- show-details: always
- spring:
- application:
- name: consul-client
- profiles:
- active: ${SPRING_PROFILES_ACTIVE}
- main:
- banner-mode: off
- cloud:
- consul:
- enabled: true
- host: consul-svc
- port: 8500
- discovery:
- instance-id: ${spring.application.name}:${random.value}
- service-name: ${spring.application.name}
- register-health-check: true
- health-check-path: /actuator/health/liveness
- health-check-interval: 60s
第 4 行的 @LoadBalanced 很重要! 這樣子才可以透過 consul 上註冊的服務名稱存取到服務。
- @Configuration
- public class RestConfig {
- @Bean
- @LoadBalanced
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
- }
- @RestController
- @RequestMapping("/wealth")
- @Slf4j
- public class WealthController {
- @Autowired
- RestTemplate rest;
- @GetMapping("/customer/{idno}")
- public ResponseEntity<?> findCustomerByIdno(@PathVariable String idno) {
- log.info("idno = " + idno);
- try {
- ResponseEntity<CustomerModel> response = rest.getForEntity("http://wealth-system/customer/" + idno, CustomerModel.class);
- CustomerModel customer = response.getBody();
- log.info(customer.toString());
- return new ResponseEntity<CustomerModel>(customer, HttpStatus.OK);
- }
- catch (Exception e) {
- log.error(e.getMessage(), e);
- return new ResponseEntity<Exception>(e, HttpStatus.INTERNAL_SERVER_ERROR);
- }
- }
- }
於 Dockerfile 所在目錄下底下的指令:
- FROM openjdk:11
- ADD consul-client.jar consul-client.jar
- ENV SPRING_PROFILES_ACTIVE sit
- EXPOSE 18080/tcp
- EXPOSE 15001/tcp
- ENTRYPOINT ["java","-jar","/consul-client.jar"]
- docker build -t twleader/consul-client .
- docker push twleader/consul-client
接著當然要部署 POD, 使用如下的 deployment yaml file:
- apiVersion: v1
- kind: Service
- metadata:
- name: consul-client-svc
- spec:
- type: LoadBalancer
- ports:
- - name: consul-client
- port: 18080
- targetPort: 18080
- - name: consul-client-management
- port: 15001
- targetPort: 15001
- selector:
- app: consul-client
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: consul-client
- spec:
- replicas: 2
- selector:
- matchLabels:
- app: consul-client
- template:
- metadata:
- name: consul-client
- labels:
- app: consul-client
- spec:
- containers:
- - image: twleader/consul-client:latest
- name: consul-client
- ports:
- - containerPort: 18080
- name: api-port
- - containerPort: 15001
- name: management-port
- env:
- - name: SPRING_PROFILES_ACTIVE
- value: "sit"
- - name: POD_IP
- valueFrom:
- fieldRef:
- fieldPath: status.podIP
- volumeMounts:
- - name: my-pvc
- mountPath: /mnt/logs
- volumes:
- - name: my-pvc
- persistentVolumeClaim:
- claimName: my-pvc
- {"idno":"E243350588","name":"Nancy","phoneNo":"0909000911","email":"nancy@yahoo.com"}
vagrant 是管理 VM 很方便的工具,先到官網下載並安裝後,建立一個內容如上檔名為 Vagrantfile 的設定檔,然後在該檔所在的路徑執行如下指令:
- Vagrant.configure("2") do |config|
- config.vm.box = "generic/centos7"
- config.vm.box_check_update = false
- config.vm.box_download_insecure = true
- config.vm.define "db-server" do |db|
- # db.vm.synced_folder "mssql", "/var/opt/mssql"
- db.vm.synced_folder "mssql", "/home/vagrant/mssql"
- db.vm.hostname = "db-server"
- db.vm.provider "virtualbox" do |vb|
- vb.gui = false
- vb.memory = 4096
- vb.cpus = 1
- end
- db.vm.network "private_network", ip: "192.168.50.13"
- end
- end
這樣會啟動一個 CentOS 7 的 VM,接著 ssh 進入該 VM, 底下將在 VM 裡安裝 MS SQL Server 2019。
- vagrant up
- vagrant ssh 192.168.50.13
# Error: Failed to download metadata for repo 'appstream': Cannot download repomd.xml: Cannot download repodata/repomd.xml: All mirrors were tried
- sudo curl -o /etc/yum.repos.d/mssql-server.repo https://packages.microsoft.com/config/rhel/7/mssql-server-2019.repo
- sudo yum install -y mssql-server.x86_64
出現綠色 active (running) 表示 sql server 正常運行
- sudo /opt/mssql/bin/mssql-conf setup
- sudo systemctl status mssql-server.service
將 vagrant 加入 mssql 群組
- sudo /opt/mssql/bin/mssql-conf set sqlagent.enabled true
- sudo usermod -aG mssql vagrant
- sudo systemctl restart mssql-server
- sudo rpm -qi mssql-server
- sudo firewall-cmd --add-port=1433/tcp --permanent
- sudo firewall-cmd --reload
- sudo curl -o /etc/yum.repos.d/msprod.repo https://packages.microsoft.com/config/rhel/7/prod.repo
- sudo yum install compat-openssl10
設定開機後 sql server 自動啟動
- sudo yum -y install mssql-tools unixODBC-devel
- sudo systemctl is-enabled mssql-server.service
# 改變預設路徑
- sudo echo 'export PATH=$PATH:/opt/mssql/bin:/opt/mssql-tools/bin' | sudo tee /etc/profile.d/mssql.sh
- source /etc/profile.d/mssql.sh
- sudo systemctl restart mssql-server
項目 | 日期 | |
---|---|---|
* |
領域驅動設計 (Domain Driven Design)
| |
1 | ||
2 | ||
* |
Design Patterns
| |
1 | ||
2 | ||
* |
Programming
| |
1 | Spring Boot Admin | 2018/02/28
|
2 | JWT 認證在 RESTful service 上的應用 (1) - JWT 說明 | 2017/08/03
|
3 | Spring Cloud Eureka: getting started | 2018/06/09
|
4 | consul: getting started | 2021/02/15
|
5 | Spring Cloud Gateway: getting started | 2021/02/18
|