Open minsu11 opened 7 months ago
Scale Up
과 Scale Out
Scale Up
: 기존 서버의 성능을 향상 시키는 방법, CPU, 메모리 업글Scale Out
: 서버를 늘려 요청을 분산하여 처리Scale up | Scale out | |
---|---|---|
확장성 | 확장에 한계 존재 | 지속적 확장이 가능 |
서버 비용 | 성능 증가에 따른 비용이 큼 | 비교적 저렴한 서버를 사용해 비용 부담이 적음 |
gateway
에서 요청이 들어오면 eureka
서버에 요청을 보냄
eureka server
에 등록된 resource server
의 health
상태 eureka server
에 자동으로 요청을 보냄eureka server
는 api server
의 상태 데이터를 gateway
에 응답
gateway
는 응답 받은 데이터를 캐싱 ![[Pasted image 20240219203256.png]]
up
상태의 서버만 gateway
에 응답함.
down
상태인 서버는 gateway
가 요청을 했을 때 응답 데이터로 안넘어감
gateway
는 다음 요청을 보낼 때까지 캐싱 된 정보로 api server
에 요청을 보냄
gateway
는 up
인 상태를 확인 한 후 api server
에 요청을 round robin
방식으로 요청 보냄
resource
서버는 30초마다 eureka
서버에 heart beat(심박수)
신호를 보냄
eureka
서버는 90초 마다 api server
에서 heart beat
가 들어오는 지 확인eureka
서버는 신호가 오지 않은 resourse
서버를 down
상태로 변경resourse
서버는 살아 있지만, eureka
서버에는 down
상태여서 gateway
는 down
상태로 표시된 서버로 요청을 보내지 않음resourse2
서버를 먼저 배포docker
에 push
한 후
resource2
서버는 eureka
서버에 본인의 health
상태를 down
으로 만드는 요청 보냄eureka
서버는 응답을 받으면 resourse2
서버의 health
상태를 down
으로 표시
down
요청을 보내고 eureka
서버에 down
로 변경이 돼도 resouce2
서버를 바로 종료시키면 안됨gateway
는 30초 마다 eureka
서버에 요청을 보내서 api
서버의 health
상태 데이터를 받은 후 캐싱down
이 되고 바로 끄게 되면 gateway
가 요청을 보내고 캐싱
을 안 했을 수 도 있음resource2
서버를 종료 시켰음에도 gateway
는 resouce2
의 상태가 up
인 상태 데이터가 캐싱
되어 있어서 resource2
서버에 요청을 보냄client
에 서버를 찾을 수 없다는 표시가 뜸resourse2
서버를 종료할 때는 gateway
에서 새롭게 캐싱
을 한 뒤에 종료 시켜야함gateway
가 resource
서버의 health
정보 요청을 eureka
서버에 보내고 응답을 받아 캐싱
gateway
가 resource1
서버로만 보내는 걸 확인 후 resourse2
서버를 종료
CI/CD
과정에서는 수동으로 볼 수 없으니 gateway
의 캐싱
시간만큼 기다렸다가 resource2
서버를 종료 시킴resource2
서버에 jar
파일 배포 후 서버 가동
eureka
서버에 up
상태로 등록
resourse1
배포gateway
가 캐싱
하여 round robin
방식으로 요청을 보내는 지 확인
gateway
가 30초 마다 캐싱
하는 시간이 있으므로 resource2
서버가 eureka
서버에 up
상태여도 캐싱
이 되지 않았으면 resource2
서버에 요청을 보내지 않음up
상태가 됐다고 바로 resource1
서버를 종료 시켜버리면 client
가 볼 땐 서버가 연결이 안됨캐싱
유무를 측정할 수 있지만, CI/CD
단계에서는 불가능 하므로 이 때도 gateway
의 캐싱
시간을 기다림gateway
가 캐싱
이 되면 resource1
은 eureka
서버에 본인의 health
상태를 down
으로 만드는 요청을 보냄
eureka
서버는 요청이 오면 resource1
서버의 health
상태를 down
으로 표시
resource2
배포 과정을 설명 할 때 했으므로 넘어감gateway
가 eureka
서버로 부터 캐싱
gateway
가 캐싱
되면 resource1
서버를 종료시키고 배포 시작
resource1
서버가 배포가 끝나고 시작이 될 때 eureka
서버에 up
으로 등록Eureka Server
인스턴스 등록CI/CD github-action
설정 할 때 나와 있으므로 생략
resource2
서버 CI/CD
또한 github-action
java
설정
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.8</spring-cloud.version>
</properties>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
EurekaApplication
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
Eureak
가 Server이므로 @EnableEurekaServer
추가SecurityConfig
@EnableWebSecurity(debug = false)
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
return http.build();
}}
eureka
에서 security
를 통해 인증된 사용자만 eureka
서버에 접속 해야하므로 security config
파일 생성application.properties
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=10s
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
Eureka
서버에 client
로 등록 여부
eureka
서버이기 때문에 eureka
서버에 registry
로 등록할 필요가 없기 때문에 false
Eureka
서버에 등록된 registry
의 정보를 가지고 올지 여부
eureka
서버에서는 registry
의 정보를 가지고 오는 건 필요가 없으므로 false
application-prod.properties
spring.security.user.name={입력 할 아이디}
spring.security.user.password={입력 할 비밀 번호}
server.port={서버 포트}
security user id, password
설정
eureka server
접속 가능eureka.server.my-url={서버 url}
eureka.client.service-url.defaultZone=http://${spring.security.user.name}:${spring.security.user.password}@{eureka 서버 IP 주소}:${server.port}/${eureka.server.my-url}/
java
설정
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.8</spring-cloud.version>
</properties>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
GatewayApplication
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
@EnableDiscoveryClient annotation
추가GatewayConfig
@Configuration
@RequiredArgsConstructor
public class GatewayConfig {
private final UrlProperties urlProperties;
/**
* methodName : customRouteLocator * author : damho-lee * description : gateway 설정. 각각 해당하는 서버로 요청 보내준다.
* auth로 들어온 요청은 auth서버로 처리한다.
* resource 서버는 Eureka를 이용해 라운드 로빈 방식으로 동작한다.
* * @param builder .
* @return route locator
*/ @Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("auth_route", r -> r.path("/auth/**")
.uri(urlProperties.getAuth()))
.route("resource-service", p -> p.path("/api/**").and()
.uri("lb://RESOURCE-SERVICE")
) .build();
}}
.route("{api 서버에서 eureka서버로 등록할 application.name의 값과 동일 해야함}", p -> p.path("/api/**").and()
.uri("lb://{위에 동일하게 api 서버에서 eureka 서버로 등록할 application.name과 동일하게 작성}")
application.properties
spring.application.name=spring-cloud-gateway-service
eureka
서버에 등록할 이름eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=gateway
eureka
에 registry
로 등록을 true
eureka
에 등록된 registry
의 정보를 eureka
로 부터 받아와야 하므로 true
eureka.instance.prefer-ip-address=true
eureka
에 등록 할 때 value 값을 gateway ip
주소로 eureka
에 등록eureka
서버에 instance-id
값 지정
eureka.client.service-url.defaultZone=http://{유레카에서 지정한 아이디}:{유레카에서 지정한 비밀 번호}@{유레카 서버 ip 주소}:{유레카 서버 포트}/{api 명세}
eureka
서버의 url
입력Resource
서버instance server startup.sh
instance
서버에 접속 한 후 administrator
로그인
result=$(curl -o /dev/null -w "%{http_code}" -X POST http://{resource ip 주소 }:{실제 서버 포트}/api/actuator/status)
echo "Result1: $result"
sleep 60 # gateway가 캐싱 되어 해당 실행된 resource 서버에 요청을 보내지 않게 기다리는 시간
docker stop resource
sleep 15 # server를 종료 시킨 후 graceful로 지정한 시간 기다림
docker rm resource
docker rmi $(docker images -q) # 서버
docker pull {docker 이미지 경로}
docker run -d --name {docker 이미지 경로} -p {java에서 지정한 port}:{실제 서버 port} -e spring.profiles.active=prod1 -e EXTERNAL_SERVER_IP={resource ip 주소} {docker 이미지 경로}
sleep 60 # 배포 후 gateway가 캐싱 되는 시간
### `java`설정
#### pom.xml
----
```java
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.8</spring-cloud.version>
</properties>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
ResourceApplication
@SpringBootApplication
@EnableDiscoveryClient
public class ResourceApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class, args);
}
}
@EnableDiscoveryClient
추가maven.yml
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Docker Image CI
on:
push:
branches: [ "main", "dev" ]
pull_request:
branches: [ "main", "dev" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# jdk 11 세팅
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: maven
# 패키징
- name: Build with Maven
run: mvn -B package --file pom.xml
# 도커 로그인
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# 도커 이미지 빌드
- name: Build the Docker image
run: docker build -t newjaehun/resource .
# 이미지 띄우기
- name: push Docker image
run: docker push newjaehun/resource
# 쉘 스크립트 실행
- name: execute shell script
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_IP }}
username: ${{ secrets.SSH_ID }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
script_stop: true
script: "./startup.sh"
- name: execute shell script
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SSH_SERVER2_IP }}
username: ${{ secrets.SSH_ID }}
key: ${{ secrets.SSH_KEY }}
port: ${{ secrets.SSH_PORT }}
script_stop: true
script: "./startup.sh"
# SonarQube 실행
- name: Run SonarQube
run: mvn sonar:sonar -Dsonar.projectKey=my-books-resource -Dsonar.host.url=${{secrets.SONAR_HOST}} -Dsonar.login=${{secrets.SONAR_TOKEN}}
resource1
, resource2
서버의 쉘 스크립트를 실행 시켜야하므로 yml
파일에 스크립트 실행 구문을 2번 넣음ApplicationStatusController
@RestController
@RequestMapping("/api/actuator/status")
public class ApplicationStatusController {
private final ApplicationInfoManager applicationInfoManager;
private final ApplicationStatus applicationStatus;
public ApplicationStatusController(ApplicationInfoManager applicationInfoManager, ApplicationStatus applicationStatus) {
this.applicationInfoManager = applicationInfoManager;
this.applicationStatus = applicationStatus;
}
@PostMapping
@ResponseStatus(value = HttpStatus.OK)
public void stopStatus() {
applicationInfoManager.setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);
applicationStatus.stopService();
}
}
api
요청이 들어오면 eureka server
에게 down
상태로 요청하고 응답 받는 controller
setInstanceStatus(InstanceInfo.InstanceStatus.DOWN)
실행 시 eureka
서버에게 down
으로 알려줌applicationStatus.stopService
실행 시 본인 서버 health
상태를 down
으로 변경ApplicationStatus
@Component
public final class ApplicationStatus {
private boolean status = true;
public void stopService() {
this.status = false;
}
public boolean getStatus() {
return status;
}}
CustomHealthIndicator
@Component
public class CustomHealthIndicator implements HealthIndicator {
private final ApplicationStatus applicationStatus;
public CustomHealthIndicator(ApplicationStatus applicationStatus) {
this.applicationStatus = applicationStatus;
}
@Override
public Health health() {
if (!applicationStatus.getStatus()) {
return Health.down().build();
} return Health.up().withDetail("service", "start").build();
}}
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=30s
spring.application.name=resource-service
application.name
eureka
서버에 표시될 registry
그룹의 이름resource
서버가 여러 개 설정이 되도 application.name
에 할당된 이름으로 표시application.name
은 gateway
경로 설정 했을 때의 값과 동일하게 작성해야함eureka.instance.lease-renewal-interval-in-seconds=30
eureka.client.fetch-registry=true
eureka.client.register-with-eureka=false
eureka.instance.lease-renewal-interval-in-seconds=30
는 resource
서버가 eureka
서버에 보내는 hart beat
시간 간격(default 30초
)eureka
서버에 registry
에 등록 여부(client이므로 true
)eureka
서버에 registry
의 정보를 가져올 지 여부management.health.status.order=DOWN, UP
up, down, out-of-service, unknown
management.health.status.order=DOWN, MAINTENANCE, UP
management.endpoint.jolokia.enabled=true
management.endpoint.metrics.enabled=true
management.endpoint.pause.enabled=true
management.endpoint.resume.enabled=true
management.endpoint.restart.enabled=true
management.endpoint.shutdown.enabled=true
end point
에 관련된 설정management.endpoints.web.exposure.include=*
endpoint
의 값을 어디까지 보여 줄 것인가(지금은 모든 값을 보여줌)eureka
에 등록하는 client
application.properties
에 eureka.instance.hostname={서버 IP주소} or {지정할 이름}
이 있어야함
eureka.instance.hostname
해주지 않고 applicaiotn.properties
에 eureka.instance.prefer-ip-address =true
해주면 eureka
에 자동으로 server ip 주소
로 등록이 됨
docker
로 ci/cd
구축 시 외부 server
주소가 아닌 docker
내부의 주소가 할당이 됨gateway
와 resource
가 서로 연동이 안됨
application.properties
에 eureka.instance.hostname
를 지정eureka.instance.prefer-ip-address =true
가 설정 값에 있으면 hostname
을 지정을 해줘도 무시를 하는 경우가 생김application.properties
에 설정 되어있는 eureka.instance.prefer-ip-address=true
설정 값을 제거 한 후 application-prod1.properties
, application-prod2.properties
에 eureka.instance.hostname={각 서버의 플로팅 주소}
를 입력
Resource 무중단 배포
Front 무중단배포