1 인스턴스 만들기

AWS(Amazon Web Services)에 가입하니 1년 동안 무료로 EC2에 t2.micro 인스턴스를 돌릴 수 있다고 하여 해스켈 웹앱을 어떻게 돌릴지 알아 보았다. 먼저 해스켈로 돌린 다는 생각에 해스켈 패키지가 잘 지원된다는 NixOS를 OS로 선택했다. 가상머신을 돌리는 서비스인 EC2에 커뮤니티 AMI(가상머신 이미지) 중 NixOS 것을 골라서 돌려 보았다. Deploying NixOS to Amazon EC2 동영상을 보고 따라했는데 어렵지 않게 할 수 있었다.

흥미로운 점은 인스턴스 만들 때 user-data를 넣어 주어 호스트 이름을 설정한다든지, 키 쌍을 새로 만들지 않고 기존의 공개키를 사용자에 대해서 설정해 주어 SSH 접속을 할 수 있게 한다든지 하는 것이었다. NixOS AMI는 user-data로 configuration.nix를 넣어 주는데 부팅할 때 이를 바탕으로 OS를 초기화하는 것 같다. 다른 OS AMI는 아마도 bash 스크립트를 user-data로 넣어 줄 수 있을 것 같은데 확실히 NixOS가 시스템 전체를 하나의 파일로 설정하는 것에서 강점을 가지는 것 같다.

2 해스켈 웹앱 배치

그 다음으로 웹앱을 인스턴스에 돌리는 방법을 찾아 보았는데 Deploying Haskell applications with ECS, Docker, and Nix | William Yao를 발견할 수 있었다. 도커 컨테이너를 이용해서 해스켈 웹앱을 EC2 인스턴스에 배치하는 것이다. 이것에 따르면 인스턴스는 어떤 리눅스 배포판 AMI이든지 별 상관이 없다. AMI는 그냥 도커 컨테이너를 돌리기만 하면 되고 해스켈에 관련된 것은 도커 컨테이너에 다 들어가 있기 때문이다.

2.1 내 PC에 NixOS 가상머신 돌리기

링크의 작업을 따라하기 위해서는 NixOS가 필요해서 NixOS 설치 이미지를 받아서 Qemu 가상머신에 설치했다. 그놈 박스(Gnome-boxes)를 이용할 수도 있고 가상 머신 관리자라는 것을 이용할 수도 있었다. 주의할 점은 루트 파일시스템 크기가 18GB 이상은 되어야 한다는 것이다. 루트 파일시스템 크기가 작으니까 도커가 이미지를 로드할 때 실패한다.

2.2 호스트에서 게스트 SSH 접속하기

설치는 잘 했는데 작업할 때 불편했다. 그래서 게스트로 SSH 접속을 하려고 했더니 방법을 몰라 검색해서 host port forward with qemu through libvirt in user-mode networking를 찾았다. 해답은 XML 파일을 고치는 것인데 ~/.config/libvirt/qemu 디렉토리에 XML 파일이 있었다. 파일을 열어 보니 직접 고치지 말고 virsh edit boxes-nixos 하라고 한다. virsh로 할 수도 있지만 가상 머신 관리자(환경설정에서 xml editing을 켜야 함)를 이용할 수도 있었다.

domain 태그에 xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0"를 추가하고 답변의 qemu:commandline 부분을 넣으니까 외부에서 ssh -p22222 localhost라고 하니까 SSH 접속이 되었다. 단, 나는 인터페이스 모델을 e1000 대신에 가상 머신 관리자에서 보이던 e1000e로 하였다. 더 나아가 답변의 코멘트로 언급된 virtio를 쓰는 방법도 알아냈다. e1000 대신 virtio-net-pci를 사용하면 된다. 그런데 이처럼 하면 인터페이스가 두 개가 생기므로 XML 파일에서 interface 하나를 지운다.

2.3 ECS 시행착오

AWS, 도커, 테라폼(Terraform) 다 처음 써 보는데 ECS가 나오기 전까지는 대체로 잘 되었지만 몇가지 문제가 발생해서 해결하였다. docker load -i result 할 때 파일시스템 공간 부족한 것은 파티션 크기 조정해서 해결했고, curl로 localhost:8000/words 접속해 볼 때 호스트에서는 안 되던 것이 NixOS의 방화벽 때문이어서 방화벽 없애서 해결했다. 도커 데몬이 실행해 있어야 하는 것을 전제로 해서 데몬을 실행해야 했고 사용자에게 권한을 주기 위해 docker 그룹에 포함시킬 필요가 있었다.

테라폼으로 AWS 리소스를 만들고 업데이트하는 것이 나오는 부분은 무료로 쓰는 상황과 나의 리전에 맞게 수정해 주었다. regionus-west-2에서 내가 쓰는 ap-northeast-2로 바꿔 주었고, instance_typet2.medium에서 t2.micro로 바꿔 주었다. amiami-0302f3ec240b9d23c로 되어 있는데 이게 뭔가 싶어서 구글에 검색하니까 Amazon Linux 2였다. 이 AMI는 ECS를 지원해서 선택되었다. 저 AMI ID는 us-west-2 리전의 것이므로 내 리전에서 비슷한 것을 골라서(최신 것으로) 그 ID를 넣어 주었다. 도커 이미지 만들 때 아마 오타인 것 같은데 haskell-cloud-add-image라고 되어 있어서 haskell-cloud-app-image로 바꾸었다. 그리고 main.tftags { 부분은 tags = {으로 고쳐 주어야 했다.

IAM 역할 ecs-instance-role을 AWS 사이트에서 직접 만들었다. 그리고 ECR에 도커 이미지를 올렸다. ECR 콘솔에 푸시 명령 보기 버튼을 누르면 올리는 방법이 나와 있는데 그대로 해서 안 된다. 이미 이미지를 빌드했기 때문에 이미지 빌드는 할 필요가 없다. docker tag 명령이 실패하는데 haskell-cloud-app:latest가 없기 때문이다. 그래서 docker images라는 명령을 치면 나오는 haskell-cloud-app-image:17yw4ndyhv0hkj4r4k8alynjkz2ng8sp와 같은 것을 대신 넣어 주었다.

그리고 main.tf의 로드밸런서 부분에서 나오는 vpc_idsubnets를 AWS 사이트에서 찾아서 넣어 주었다. vpc_id는 인스턴스 설명에 나와서 넣을 수 있었지만, subnets는 두 개의 서브넷을 넣어야 하는데 인스턴스 설명에는 하나밖에 없다. 서브넷 ID를 클릭해서 따라가면 서브넷 목록이 나온다. 검색에 서브넷 ID가 있어서 하나밖에 안 보이는데 그걸 지우면 세 개가 보인다. 거기서 두 개를 subnets에 넣어 주었다.

그리고 terraform apply 할 때 경고가 많이 나오는데 "${xxx.yyy}" 문법이 테라폼 새 버전에서는 없어졌고 그냥 xxx.yyy만 쓰라고 한다. 이렇게 하고 terraform apply를 했는데 웹앱이 안 돈다. 그건 바로 haskell-cloud-app-service.json 안에 memory1024로 주었기 때문이다. t2.micro는 1GB 메모리인데 앱에 이걸 주었으니 메모리가 부족한 것이다. 그래서 1024512로 바꾸어서 다시 terraform apply 하니까 웹앱이 돈다. 나중에 알았지만 ECS 콘솔에 가면 서비스에 대한 이벤트를 볼 수 있다.

별 도움은 안 되었지만 인스턴스에 접속해서 문제점을 찾기 위해 다음과 같이 main.tf를 고쳐 인스턴스에 key_name을 주고 SSH 22번 포트를 열었다. aws-ssh-key는 앞서 NixOS 인스턴스를 만들면서 생성한 키 페어이다.

resource "aws_security_group" "ssh-access" {
  name = "ssh-access"

  ingress {
    from_port = 22
    to_port = 22
    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"]
  }
}

resource "aws_ecs_cluster" "haskell-cloud-app" {
  name = "haskell-cloud-app"
}

resource "aws_instance" "haskell-cloud-app" {
  ami = "ami-0fa5d85859452a178"
  instance_type = "t2.micro"
  vpc_security_group_ids = [
    "${aws_security_group.haskell-cloud-app-access.id}",
    "${aws_security_group.ssh-access.id}"
  ]

  key_name = "aws-ssh-key"

  tags = {
    Name = "haskell-cloud-app"
  }

  .......

3 그 다음

나는 HTTPS를 지원하게 하고 싶다. 블로그 글에 로드밸런서에서 HTTPS를 지원하게 할 수 있다고 써 있다. 이것을 좀 더 알아 본 후에 그냥 NixOS에도 ecs-agent가 있는 것 같으므로 Nginx와 SSL 인증서는 configuration.nix로 한 번에 설정하고 내 웹앱만 도커 이미지로 관리할 수 있는지 알아 보아야겠다.