Tutorial for Use AWS CodeBuild with Jenkins #1


Jenkins 와 AWS Codebuild를 연동하는 방법을 소개하고자 한다.

관련 Posting은 많이 있으나 그것들은 초보가 보고 따라하기에는 생략되어 있는 과정, 설명이 너무나도 많다. 관련된 자료들을 모아서 Posting 하겠다.

Tutorial에서 진행할 내용

Node.JS + Nest.JS로 작성된 Dummy Server를 Github Repository에 배포한다. (이건 직접적으로 다루지 않고, Repository Link를 제공하겠다.) EC2에 Jenkins를 설치하고, Multibranch Pipeline Project를 생성한다. Pipeline은 Jenkinsfile로 작성을 하며, Codebuild와 연동한다. Codebuild에서는 buildspec 파일을 참조하여 실행을 하며, Server를 Docker Image로 만들어서 ECR에 배포한다.

이번 Posting에서는 여기까지 다룰 것이며, 추후 ECR 이미지를 Kubernetes (EKS)로 배포하는 과정을 소개할 것이다.

참고로 Posting에 사용된 Jenkins 버전은 2.235.5이며, 2020년 8월 21일의 상황이다. AWS의 Seoul Region을 이용할 것이며, Available Zone이 a, b, c, d 총 4개가 있다.

사용한 Tool들의 버전은 다음과 같다.

  • Node : v12.18.3
  • Yarn : 1.22.4
  • Terraform : v0.13.0
  • AWS CLI : aws-cli/2.0.39 Python/3.7.4 Darwin/19.6.0 exe/x86_64yar

위 tool들은 모두 설치가 되어 있으며, ~/.aws/credentials에 AWS ACCESS KEY 와 SECRET KEY가 저장되어 있다고 가정하겠다.

개인적으로는 이 과정에서 가장 어려웠던 것은

  1. Codebuild를 위한 Private Subnet 생성
  2. AWS에서 제공하는 Codebuild용 Docker Image가 작년과 달라져서 생긴 혼란
  3. AWS CLI 버전업에 따른 command 변경으로 생긴 혼란

이 글에서 다룰 내용

  • Terraform을 이용하여 관련 Resource들을 AWS에 배포
  • Private Subnet 생성 방법
  • AWS EC2에 Jenkins 설치
  • Jenkins Multibranch Pipeline Project 생성
  • Jenkins와 Slack 연동
  • Jenkinsfile 작성
  • Codebuild용 buildspec 작성

이번 Posting에서 사용한 Server Code

Node.JS + Nest.JS로 작성된 예제코드를 사용하겠다. 그냥 Hello World 라는 문자열을 Response하는 것이 전부이다.

1. EC2에 Jenkins 설치

1. 관련 AWS Resource 배포

EC2에 Jenikns를 배포하기 위해서 필요한 AWS Resource는 다음과 같다.

EC2를 배포하기 위해서는, Subnet, Security Group, Role(instance profile)이 필요하고, Subnet을 배포하기 위해서는 VPC가 필요하다. Subnet을 Internet망에 연결시키기 위해서는 Route Table에 연결시켜야 하며, VPC에 Internet Gateway를 생성해야 한다. 벌써부터 머리가 지끈 아플 것이다. 걱정 할 것없다. 아래에 관련 Resource 들을 다 정의해 두었다. Jenkins의 원할한 사용을 위해 t3.medium 으로 설치를 진행 할 것이다. AMI 이미지는 Amazon Linux 2 최신을 사용하겠다.

일단 Terraform을 실행할 AWS Region을 정의 한다.

  • provider.tf
    provider "aws" {
    region     = "ap-northeast-2"
    }
    

이제 VPC, Subnet, Route Table, Internet Gateway 등 Network Resource들을 정의한다. 참고로 CIDR Block값은 각자 상황에 맞게 수정을 해야 한다.

resource "aws_vpc" "tutorial" {
  cidr_block           = "172.32.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = { 
    Name = "Tutorial" 
  }
}
  • subnet.tf
resource "aws_subnet" "public_a" {
  vpc_id            = aws_vpc.tutorial.id
  cidr_block        = "172.32.0.0/20"
  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "Tutorial Subnet A"
  }
}

resource "aws_subnet" "public_b" {
	vpc_id            = aws_vpc.tutorial.id
	cidr_block        = "172.32.16.0/20"
	availability_zone = "ap-northeast-2b"

	tags = { 
    Name = "Tutorial Subnet B"
  }
}

resource "aws_subnet" "public_c" {
	vpc_id            = aws_vpc.tutorial.id
	cidr_block        = "172.32.32.0/20"
	availability_zone = "ap-northeast-2c"

	tags = { 
    Name = "Tutorial Subnet C"
  }
}

resource "aws_subnet" "public_d" {
	vpc_id            = aws_vpc.tutorial.id
	cidr_block        = "172.32.48.0/20"
	availability_zone = "ap-northeast-2d"

	tags = { 
    Name = "Tutorial Subnet D" 
  }
}
  • route_table.tf
resource "aws_default_route_table" "tutorial" {
  default_route_table_id = aws_vpc.tutorial.default_route_table_id

  tags = { Name = "Tutorial Public Route Table" }
}

resource "aws_route_table_association" "public_a" {
	subnet_id      = aws_subnet.public_a.id
	route_table_id = aws_vpc.tutorial.default_route_table_id
}

resource "aws_route_table_association" "public_b" {
	subnet_id      = aws_subnet.public_b.id
	route_table_id = aws_vpc.tutorial.default_route_table_id
}

resource "aws_route_table_association" "public_c" {
	subnet_id      = aws_subnet.public_c.id
	route_table_id = aws_vpc.tutorial.default_route_table_id
}

resource "aws_route_table_association" "public_d" {
	subnet_id      = aws_subnet.public_d.id
	route_table_id = aws_vpc.tutorial.default_route_table_id
}
  • internet_gateway.tf
resource "aws_internet_gateway" "tutorial" {
  vpc_id = aws_vpc.tutorial.id

  tags = { Name = "Tutorial Internet Gateway" }
}

resource "aws_route" "tutorial" {
	route_table_id         = aws_vpc.tutorial.default_route_table_id
	destination_cidr_block = "0.0.0.0/0"
	gateway_id             = aws_internet_gateway.tutorial.id
}

이제, Jenkins를 설치할 EC2를 정의한다. EC2를 배포하기 위해서는 Security Group을 정의해야 하며, IAM의 Role과 Instance Profile을 정의해야 한다. Role에 AmazonSSMManagedInstanceCore라는 Policy를 추가하였는데, 이건 Session Manager를 이용해서 EC2에 접속하기 위함이다. 이 Policy를 제외하고 SSH Terminal을 통해서 접속하거나 EC2 Instance Connect (browser-based SSH connection)로 접속을 해도 되지만, Session Manager를 이용하면 접속 내역에 대해서 조회가 가능하며, Slack으로 Notify하는 것도 가능하기 때문에 관리적인 측면에서 좋다.

  • security_group.tf
resource "aws_security_group" "ci_master" {
  name        = "totorial_ci_master"
  description = "Tutorial CI Master"
  vpc_id      = aws_vpc.tutorial.id

  ingress {
    from_port       = 0
    to_port         = 65535
    protocol        = "tcp"
    cidr_blocks     = ["0.0.0.0/0"]
  }

  egress {
    from_port       = 0
    to_port         = 0
    protocol        = "-1"
    cidr_blocks     = ["0.0.0.0/0"]
  }

  tags = { 
    Name = "Tutorial CI Master"
  }
}
  • iam_role.tf
resource "aws_iam_role" "ci" {
    name               = "tutorial_ci"
    path               = "/"
    assume_role_policy = <<POLICY
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}
  • instance_profile.tf
resource "aws_iam_instance_profile" "ci" {
  name = "tutorial_ci"
  role = aws_iam_role.ci.name
}
  • policy_attachment.tf
resource "aws_iam_policy_attachment" "AmazonSSMManagedInstanceCore" {
  name       = "AmazonSSMManagedInstanceCore"
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  roles      = [ "AmazonSSMRoleForInstancesQuickSetup", "${aws_iam_role.ci.name}" ]
}

필자의 경우에는 이번 Tutorial을 진행하기 전부터 Session Manager를 사용하고 있었으므로 AmazonSSMRoleForInstancesQuickSetup Role에 이미 할당되어 있는 상태여서 추가하였다.

  • ec2.tf
resource "aws_instance" "jenkins_master" {
  ami                         = "ami-0bd7691bf6470fe9c"
  ebs_optimized               = false
  instance_type               = "t3.medium"
  monitoring                  = false
  key_name                    = ""
  subnet_id                   = aws_subnet.public_a.id
  vpc_security_group_ids      = [ aws_security_group.ci_master.id ]
  iam_instance_profile        = aws_iam_instance_profile.ci.name
  associate_public_ip_address = true
  source_dest_check           = true

  root_block_device {
    volume_type           = "gp2"
    volume_size           = 100
    delete_on_termination = true
  }

  tags = { 
    Name = "Tutorial Jenkins Master"
  }
}

해당 폴더에서 아래 명령어로 배포를 진행한다.

terraform init
terraform apply

EC2 콘솔로 이동한 다음 생성한 EC2를 선택 -> Connect -> 원하는 방식으로 접속 (필자의 경우에는 Session Manager)

이렇게 진행해서 shell이 보이면 정상적으로 AWS Resource 들이 다 생성된 것이다.

만약 Session Manager를 한 번도 사용하지 않은 경우 따로 설정이 필요 할 수도 있다. 아래 Link로 이동하여 설정하면 된다.

2. Jenkins 설치

Shell로 접속한 다음 아래 명령어 들을 실행한다.

먼저 yum을 최신버전으로 update 해준다.

sudo yum update -y

Jenkins repo를 yum에 추가한 다음 Jenkins를 설치한다.

sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
sudo yum install jenkins -y

Git이 설치되지 않은 Linux이므로 설치해 준다.

sudo yum install git -y

Jenkins를 실행 (서비스에 등록)한다.

sudo service jenkins start

만약 오류가 난다면 Java 8버전 이상을 설치해 줘야 한다.

sudo yum install -y java-1.8.0-openjdk-devel.x86_64
java -version

혹시 Java가 2개 이상 설치된 경우 어떤 것을 사용할 것이며 사용하지 않을 것을 지우고자 한다면 아래 명령어들을 참조해서 실행할 수 있다.

sudo /usr/sbin/alternatives --config java
sudo yum remove java-1.7.0-openjdk -y

이제 다시 Jenkins를 실행해보자.

sudo service jenkins start

정상실행 되었다는 메세지가 보이면 EC2 주소를 확인해서 접속해보자. Port 8080을 사용한다.

e.g. http://ec2-52-78-5-98.ap-northeast-2.compute.amazonaws.com:8080

처음 접속하면 Unlock Jenkins 라는 창이 뜬다. 초기 패스워드를 입력해야 한다.

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

위 명령어로 확인한 값을 입력하자.

Customize Jenkins 에서는 Install suggested plugins를 누란다. 우리가 필요한 plugin들은 여기서 설치를 못하므로 그때 같이 설치해주면 된다.

모든 준비가 완료되면 최초 관리자계정을 생성하는 화면이 뜬다. 본인의 계정을 생성한다. Jenkins URL은 일단 그대로 둔다. 추후 Route53등을 이용하여 URL을 변경 할 경우에는 Jenkins 관리에서 수정이 가능하다. Save and Finish를 누른다.

이제 앞으로 사용할 plugin들을 미리 설치해둔다. Jenkins 관리 -> 플러그인 관리 -> 설치 가능

  • Slack Notification
  • AWS CodeBuild
  • Blue Ocean : Blue Ocean이 들어간 plugin이 엄청많다. 그냥 Blue Ocean을 설치하면 나머지는 자동으로 같이 설치된다.
  • Google Login : Google 계정으로 로그인을 가능하게 해준다. 특정 domain 의 Google 계정만 접속하게 하면 회사안에서 사용하기 편리하다. 이 기능을 사용하지 않을 경우에는 설치할 필요가 없다.

지금 다운로드하고 재시작 후 설치하기 를 누른다.

마치며…

이제 Jenkins가 정상적으로 재실행되는 것을 확인하는 것으로 이번 Tutorial #1 은 마무리 하겠다.

다음편에서는 AWS CodeBuild Project와 이에 필요한 Resource들을 Terraform을 이용하여 배포하고, Multibranch Pipeline Project를 생성하여 실제로 Build가 되는 과정까지 다룰 것이다.

Reference


이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)