최근에 Jenkins 서버의 용량이 가득 차면서 함께 올라가있던 다른 서비스들도 멈춰버리는 문제점이 발견되었습니다. 그래서 그 중 하나인 Nexus를 분리하고 Blob Store를 Local Storage가 아닌 S3로 설정하여 비용 절감을 꾀하기로 합니다. 기왕 이렇게 하기로 결정하였으니, ECS에 서비스 형태로 올려보고자 합니다.

EFS 설정하기

ECS의 Fargate는 EBS를 지원하지 않습니다. 대신에 조금 더 비싼 요금을 자랑하는 EFS를 볼륨으로 제공합니다. EFS는 AWS의 NFS (Network File System)라고 보시면 됩니다. 가격이 비싸기는 하지만, Nexus의 아티팩트들은 S3에 저장할 것이므로 시스템의 필수 파일들 0.5GB 이내만 EFS에 저장될 예정이고 그 비용은 결코 크지 않습니다.

EFS는 공통으로 사용하므로 Terraform을 이용하지 않고 미리 생성하였습니다. 그 후에 다음과 같이 Access Point를 설정합니다.

resource "aws_efs_access_point" "efs" {
file_system_id = var.efs_id
posix_user {
gid = var.efs_gid
uid = var.efs_uid
}root_directory {
creation_info {
owner_gid = var.efs_gid
owner_uid = var.efs_uid
permissions = “755”
}
path = “/nexus”
}
}

Code language: JavaScript (javascript)

Nexus 전용 Path를 결정하고, Nexus 도커 이미지에서 마운트하여 사용될 nexus-data 경로의 소유권과 권한을 입력합니다. 이미지에 따라서 다를 수 있기 때문에 반드시 사전에 Nexus 도커 이미지를 실행하여 nexus-data 폴더가 어떤 소유권과 권한을 사용하고 있는지 파악하는 것이 중요합니다. 공식 이미지에서 소유권은 1000, 권한은 755를 사용하고 있습니다.

ECS 작업 설정하기

이제, ECS의 작업에서 EFS를 마운트할 수 있도록 설정을 추가해야 합니다. 특히, Nexus는 동시에 열 수 있는 최대 파일 갯수를 65536 이상으로 변경해줘야 하는데, 이러한 요구사항도 작업에 반영해야 합니다.

locals {
port_mappings = [
{
containerPort = var.task_port
protocol = "tcp"
}
] mount_points = [
{
sourceVolume = "nexus"
containerPath = "/nexus-data"
}
] ulimits = [
{
name = "nofile"
softLimit = 65536
hardLimit = 65536
}
]
container_definition = {
name = local.name_ecs_container
image = “sonatype/nexus3”
essential = true
memory = var.task_memory
portMappings = toset(local.port_mappings)
ulimits = toset(local.ulimits)
mountPoints = toset(local.mount_points)
container_definition_json = jsonencode(local.container_definition)
}

Code language: JavaScript (javascript)

위와 같이 컨테이너 정의를 생성하고, 이를 작업에 추가합니다.

resource "aws_ecs_task_definition" "new" {
...
container_definitions = “[${local.container_definition_json}]”volume {
name = “nexus”

efs_volume_configuration {
file_system_id = var.efs_id
root_directory = “/”
transit_encryption = “ENABLED”

authorization_config {
iam = “ENABLED”
access_point_id = aws_efs_access_point.efs.id
}
}
}
}

Code language: JavaScript (javascript)

마지막으로, IAM 권한이 있어야 마운트가 가능합니다.

resource "aws_iam_role_policy" "efs" {
role = aws_iam_role.ecs_task.name
policy = jsonencode({
Version = “2012-10-17”
Statement = [
{
Action = [
“elasticfilesystem:ClientMount”,
“elasticfilesystem:ClientWrite”,
“elasticfilesystem:ClientRootAccess”
] Effect = “Allow”
Resource = [
“arn:${var.partition}:elasticfilesystem:${var.region}:${var.account}:file-system/${var.efs_id}”
] }
] })
}
Code language: JavaScript (javascript)

작업에서 EFS를 nexus 볼륨으로 추가하였으므로, 컨테이너에서는 nexus 볼륨을 /nexus-data 경로로 마운트시키면 됩니다. EFS의 Access Point가 적절한 소유권과 권한으로 설정이 되었다면, 별다른 오류 없이 Nexus 서비스가 구동될 것입니다.

S3 연동하기

Nexus를 S3에 연동하기 위해서는 ECS 작업에 S3 관련 권한이 필요합니다. 다음과 같이 IAM 권한을 추가합니다.

resource "aws_iam_role_policy" "s3" {
role = aws_iam_role.ecs_task.name
policy = jsonencode({
Version = “2012-10-17”
Statement = [
{
Action = [
“s3:PutObject”,
“s3:GetObject”,
“s3:DeleteObject”,
“s3:ListBucket”,
“s3:GetLifecycleConfiguration”,
“s3:PutLifecycleConfiguration”,
“s3:PutObjectTagging”,
“s3:GetObjectTagging”,
“s3:DeleteObjectTagging”,
“s3:GetBucketAcl”,
“s3:CreateBucket”
] Effect = “Allow”
Resource = [
“arn:aws:s3:::${local.name_s3}”,
“arn:aws:s3:::${local.name_s3}/*”
] }
] })
}
Code language: JavaScript (javascript)

본래 DeleteBucket 권한도 제공하도록 가이드가 제공되고 있지만, 저는 실수로라도 버킷이 지워지는 것을 원하지 않기 때문에 해당 권한은 제외하였습니다.

권한 부여가 완료되었으면, 이제부터는 Nexus 내의 설정이 필요합니다. 관리자 설정에서 Blob Store를 생성하고 S3 타입을 지정합니다.

S3 이름만 넣고 생성하면 바로 해당 S3를 생성하게 됩니다.

생성이 완료되었으면, 이제 리포지토리를 연동해야 합니다. 아쉽게도 기본적으로 제공되는 모든 리포지토리는 Local 디스크로 연결되는 기본 타입의 Blog Store가 연동되어 있습니다. 그리고 리포지토리는 한 번 설정하게 되면 Blob Store의 변경이 되지 않습니다. 그러므로, 모든 리포지토리를 삭제하고 다시 생성합니다. 다시 생성하면서 S3 Blob Store를 연결하면 됩니다.

만약에, 기존 Blob Store를 S3로 변경하려고 한다면, Nexus 내부의 OrientDB에서 직접 정보를 수정함으로써 변경할 수도 있다고 합니다. 이렇게 하려면 미리 S3를 생성, 데이터를 마이그레이션하고 레코드만 업데이트 하면 된다고 합니다. (참고 문서)

참고 사항

  • Nexus의 관리자 암호는 nexus-data 폴더 안에 파일로 생성되는데, EFS에 연동하였으므로 ECS Exec 기능을 이용하여 컨테이너에 직접 접속하거나 (참고 문서) EC2에서 EFS를 추가 연결함으로써 확인이 가능합니다.
  • S3를 이용하면 Nexus의 퍼포먼스가 떨어지게 됩니다. Jenkins와 같은 네트워크에 놓고 S3를 Private Endpoint로 연결하면 조금 나아지겠지만, 비용과의 Trade-off라고 생각하고 있습니다.
  • Nexus 업그레이드를 위해서 ECS 서비스를 업데이트하면 일반적으로 기존 작업이 남아있는 상태에서 추가 작업을 실행, 정상 실행시 대체하게 되는데, EFS 파일 세트에는 동시 접근이 가능하더라도 추가 실행된 넥서스가 파일을 점유하지 못하여 실행되지 않습니다. 그래서 무중단 업그레이드는 어렵습니다.