diff --git a/backend/console-server/docker-compose.yml b/backend/console-server/docker-compose.yml index 066714cc..3cfea514 100644 --- a/backend/console-server/docker-compose.yml +++ b/backend/console-server/docker-compose.yml @@ -4,11 +4,15 @@ services: image: nginx:latest ports: - "80:80" + - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf + - /etc/letsencrypt:/etc/letsencrypt depends_on: - server-blue - server-green + networks: + - app-network server-blue: image: ghcr.io/boostcampwm-2024/web35-watchducks/backend/console-server:latest @@ -24,6 +28,8 @@ services: interval: 10s timeout: 2s retries: 5 + networks: + - app-network server-green: image: ghcr.io/boostcampwm-2024/web35-watchducks/backend/console-server:latest @@ -39,3 +45,10 @@ services: interval: 10s timeout: 2s retries: 5 + networks: + - app-network + +networks: + app-network: + name: app-network + driver: bridge \ No newline at end of file diff --git a/backend/console-server/nginx.conf b/backend/console-server/nginx.conf index 9c43fd50..b3da4e3f 100644 --- a/backend/console-server/nginx.conf +++ b/backend/console-server/nginx.conf @@ -4,8 +4,24 @@ http { server server-green:3002 backup; } + # http를 https로 리디렉션 server { listen 80; + server_name watchducks-test.store; + + location / { + return 301 https://$host$request_uri; + } + } + + server { + listen 443 ssl; + server_name watchducks-test.store; + + ssl_certificate /etc/letsencrypt/live/watchducks-test.store/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/watchducks-test.store/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://console_server; diff --git a/backend/console-server/src/clickhouse/util/metric-expressions.ts b/backend/console-server/src/clickhouse/util/metric-expressions.ts index b29df629..e3017b65 100644 --- a/backend/console-server/src/clickhouse/util/metric-expressions.ts +++ b/backend/console-server/src/clickhouse/util/metric-expressions.ts @@ -8,6 +8,7 @@ export const metricExpressions: Record = { max: (metric: string) => `max(${metric}) as ${metric}`, p95: (metric: string) => `quantile(0.95)(${metric}) as ${metric}`, p99: (metric: string) => `quantile(0.99)(${metric}) as ${metric}`, + rate: (metric: string) => `(sum(${metric}) / count(*)) * 100 as ${metric}_rate`, }; export type MetricAggregationType = keyof typeof metricExpressions; diff --git a/backend/console-server/src/log/log.controller.ts b/backend/console-server/src/log/log.controller.ts index 3b4d31df..c84d82fe 100644 --- a/backend/console-server/src/log/log.controller.ts +++ b/backend/console-server/src/log/log.controller.ts @@ -42,8 +42,34 @@ export class LogController { async trafficRank() { return await this.logService.trafficRank(); } -} -// 1. 기수 내 전체 프로젝트 -// 2. 기수 내 총 트래픽 -// 4. 기수 내 응답 성공률 + @Get('/response-rate') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: '기수 내 응답 성공률', + description: '요청받은 기수의 기수 내 응답 성공률를 반환합니다.', + }) + @ApiResponse({ + status: 200, + description: '기수 내 응답 성공률이 성공적으로 반환됨.', + type: ProjectResponseDto, + }) + async responseSuccessRate() { + return await this.logService.responseSuccessRate(); + } + + @Get('/traffic') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: '기수 내 총 트래픽', + description: '요청받은 기수의 기수 내 총 트래픽를 반환합니다.', + }) + @ApiResponse({ + status: 200, + description: '기수 내 총 트래픽가 정상적으로 반환됨.', + type: ProjectResponseDto, + }) + async trafficByGeneration() { + return await this.logService.trafficByGeneration(); + } +} diff --git a/backend/console-server/src/log/log.repository.ts b/backend/console-server/src/log/log.repository.ts index 7fed76a2..3d9a1283 100644 --- a/backend/console-server/src/log/log.repository.ts +++ b/backend/console-server/src/log/log.repository.ts @@ -48,4 +48,36 @@ export class LogRepository { return await this.clickhouse.query(query, params); } + + async findResponseSuccessRate() { + const { query, params } = new TimeSeriesQueryBuilder() + .metrics([ + { + name: 'is_error', + aggregation: 'rate', + }, + ]) + .from('http_log') + .build(); + + const result = await this.clickhouse.query(query, params); + return { + success_rate: 100 - (result as Array<{ is_error_rate: number }>)[0].is_error_rate, + }; + } + + async findTrafficByGeneration() { + const { query, params } = new TimeSeriesQueryBuilder() + .metrics([ + { + name: '*', + aggregation: 'count', + }, + ]) + .from('http_log') + .build(); + + const result = await this.clickhouse.query(query, params); + return result[0]; + } } diff --git a/backend/console-server/src/log/log.service.ts b/backend/console-server/src/log/log.service.ts index f57eac22..bdc81a6a 100644 --- a/backend/console-server/src/log/log.service.ts +++ b/backend/console-server/src/log/log.service.ts @@ -26,4 +26,16 @@ export class LogService { return result.slice(0, 4); } + + async responseSuccessRate() { + const result = await this.logRepository.findResponseSuccessRate(); + + return result; + } + + async trafficByGeneration() { + const result = await this.logRepository.findTrafficByGeneration(); + + return result; + } } diff --git a/backend/console-server/src/project/dto/count-project-by-generation-response.dto.ts b/backend/console-server/src/project/dto/count-project-by-generation-response.dto.ts index de95b544..33e488b2 100644 --- a/backend/console-server/src/project/dto/count-project-by-generation-response.dto.ts +++ b/backend/console-server/src/project/dto/count-project-by-generation-response.dto.ts @@ -2,13 +2,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; export class CountProjectByGenerationResponseDto { - @ApiProperty({ - example: '5', - description: '부스트캠프 기수', - }) - @Type(() => Number) - generation: number; - @ApiProperty({ example: '42', description: '해당 기수의 프로젝트 총 개수', diff --git a/backend/console-server/src/project/project.service.ts b/backend/console-server/src/project/project.service.ts index 728f6466..4b4af9da 100644 --- a/backend/console-server/src/project/project.service.ts +++ b/backend/console-server/src/project/project.service.ts @@ -60,7 +60,6 @@ export class ProjectService { }); return plainToInstance(CountProjectByGenerationResponseDto, { - generation: generation, count: count, }); } diff --git a/backend/name-server/src/server/utils/dns-response-builder.ts b/backend/name-server/src/server/utils/dns-response-builder.ts index 21c5a24c..33662374 100644 --- a/backend/name-server/src/server/utils/dns-response-builder.ts +++ b/backend/name-server/src/server/utils/dns-response-builder.ts @@ -52,7 +52,7 @@ export class DNSResponseBuilder { name: question.name, type: 'A', class: 'IN', - ttl: 300, + ttl: 10, data: this.config.proxyServerIp, }, ]; diff --git a/backend/proxy-server/src/server/proxy-server.ts b/backend/proxy-server/src/server/proxy-server.ts index d80c4aa2..9712aaec 100644 --- a/backend/proxy-server/src/server/proxy-server.ts +++ b/backend/proxy-server/src/server/proxy-server.ts @@ -39,6 +39,9 @@ export class ProxyServer { connections: Number(process.env.DEFAULT_CONNECTIONS), pipelining: Number(process.env.DEFAULT_PIPELINING), keepAliveTimeout: Number(process.env.DEFAULT_KEEP_ALIVE), + connect: { + rejectUnauthorized: false, + }, }, }); } @@ -89,7 +92,7 @@ export class ProxyServer { private async executeProxyRequest(request: FastifyRequest, reply: FastifyReply): Promise { const host = validateHost(request.headers[HOST_HEADER]); const ip = await this.resolveDomain(host); - const targetUrl = buildTargetUrl(ip, request.url, 'http://'); // TODO: Protocol 별 arg 세팅 + const targetUrl = buildTargetUrl(ip, request.url, 'https://'); // TODO: Protocol 별 arg 세팅 await this.sendProxyRequest(targetUrl, request, reply); }