처음이라도 할 수 있는, 안전한 환경을 세우는 방법【EC2・Nginx・Unicorn】

환경 구축에 있어서, public에 web기・private에 App기와 나누는 쪽이 보다 시큐리티 강화에 연결된다고 하는 것으로 그 설계로 rails의 환경을 세워 보았으므로 정리해 보겠습니다.

소개



전체 그림은 이런 느낌입니다.

설정합니다.

nginx 측



EC2 서버를 설치하고 nginx를 설치합니다.
# yum -y install nginx

설치 후, nginx의 설정을 만나갑니다.
이 파일은/etc/nginx/nginx.conf에서 작성되었습니다.
코드는 전체적으로 이런 느낌이었던 것 같은... 다르면 죄송합니다.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.fedora.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        set_real_ip_from 172.200.0.0/21;
        real_ip_header  X-Forwarded-For;

        client_max_body_size 100m;
        error_page 404 /404.html;
        error_page 500 502 503 504 /500.html;
        try_files $uri/index.html $uri @unicorn;

        location / {
          proxy_pass http://xxx.xxx.xxx.xxxx:8080;
        }


    }
}


괴롭히는 것은 server의 곳입니다. 꺼내 보겠습니다.
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        set_real_ip_from xxx.xxx.xxx.xxxx/21; #←VPCのIPを設定
        real_ip_header  X-Forwarded-For;

        client_max_body_size 100m;
        error_page 404 /404.html;
        error_page 500 502 503 504 /500.html;
        try_files $uri/index.html $uri @unicorn;

        location / {
          proxy_pass http://xxx.xxx.xxx.xxxx:8080;#←privateインスタンスのIPを設定、ポート番号も設定する
        }


    }


수정해야 할 것은 위치 부분입니다. (set_real_ip_from과 real_ip_header는 ALB를 통과했을 때 여기의 코드가 없으면 ALB의 IP 주소가 되어 버리므로, 자사로부터의 액세스인지 어떤지를 판단할 수 있도록 넣고 있습니다)
거기에 지정하면 포트 8080에서 개인 인스턴스에 요청을 전달할 수 있습니다.

시작할 때는 다음 명령입니다.
# nginx

유니콘



Gemfile에 넣고 bundle install하면됩니다.
gem 'unicorn'

unicorn 설정은 다음과 같습니다.
파일은 config/unicorn.rb
$unicorn_user = "user"
$unicorn_group = "user"

worker_processes 16
working_directory '/var/www/app'

listen      '/tmp/unicorn.sock', :backlog => 1024
listen      8080, :tcp_nopush => true #←ここを8080にする
timeout     120

pid         "/tmp/pids/unicorn.pid"
stderr_path "/var/log/unicorn/unicorn_error.log"
stdout_path "/var/log/unicorn/unicorn.log"

preload_app  true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

  old_pid = "#{ server.config[:pid] }.oldbin"
  unless old_pid == server.pid
    begin
      Process.kill :QUIT, File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end


포트 번호를 nginx로 전달하는 포트에 맞춥니다.

시작할 때는 다음 명령입니다.
bundle exec unicorn_rails -c config/unicorn.rb -E development -D

kill 할 때는 다음과 같습니다. unicorn 설정의 pids와 일치합니다.
kill -QUIT `cat /tmp/pids/unicorn.pid`

마지막으로



이제 안전한 환경을 만들 수 있습니다. 같은 환경을 세울 때 귀찮아서 cloudformation으로 만들고 싶습니다.

nginx 구성 파일을 다시 작성합니다.
    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  localhost;

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        set_real_ip_from xxx.xxx.xxx.xxxx/21; #←VPCのIPを設定
        real_ip_header  X-Forwarded-For;

        client_max_body_size 100m;
        error_page 404 /404.html;
        error_page 500 502 503 504 /500.html;
        try_files $uri/index.html $uri @unicorn;

        location / {
          proxy_pass http://private_ip_address:8080;#←privateインスタンスのIPを設定、ポート番号も設定する
        }


    }


위치를 private_ip_address로 수정했습니다. 이런 방식으로 cloudformation 작업을 실행할 때 대체하고 IP를 넣는 전략입니다. 템플릿은 다음과 같습니다.
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Green CloudFormation Template for User Production",
  "Parameters": {
    "KeyName": {
      "Type": "AWS::EC2::KeyPair::KeyName"
    },
    "ALBName": {
      "Type": "String",
      "Description": "Production ALBName"
    },
    "LoadBalancerArn": {
      "Type": "String",
      "Description": "Production ALBName"
    },
    "VPCID": {
      "Type": "String",
      "Description": "Production VPC"
    },
    "PublicSubnetB": {
      "Type": "String",
      "Description": "Public SubnetId B"
    },
    "PrivateSubnetB": {
      "Type": "String",
      "Description": "Private Subnet B"
    },
    "InstanceSecurityGroupWeb": {
      "Type": "String",
      "Description": "WebServer SecurityGroup"
    },
    "InstanceSecurityGroupApp": {
      "Type": "String",
      "Description": "WebServer SecurityGroup"
    },
    "SSHGroup": {
      "Type": "String",
      "Description": "SSH SecurityGroup"
    }
  },
  "Resources": {
    "PrivateEc2Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": "ami-xxxxxxxx",
        "InstanceType": "c3.2xlarge",
        "IamInstanceProfile": "ApplicationServer",
        "SecurityGroupIds": [
          {
            "Ref": "InstanceSecurityGroupApp"
          },
          {
            "Ref": "SSHGroup"
          }
        ],
        "SubnetId": {
          "Ref": "PrivateSubnetB"
        },
        "KeyName": {
          "Ref": "KeyName"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "ApplicationServer"
          }
        ],
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "#!/bin/bash\n",
                "sudo su\n",
                "su user \n",
                "cd \n",
                "sudo dd if=/dev/zero of=/swapfile bs=1M count=1024\n",
                "sudo mkswap /swapfile\n",
                "sudo swapon /swapfile\n"
              ]
            ]
          }
        }
      }
    },
    "PublicEc2Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId": "ami-xxxxxxx",
        "InstanceType": "t2.small",
        "SecurityGroupIds": [
          {
            "Ref": "InstanceSecurityGroupWeb"
          },
          {
            "Ref": "SSHGroup"
          }
        ],
        "SubnetId": {
          "Ref": "PublicSubnetB"
        },
        "KeyName": {
          "Ref": "KeyName"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "WebServer"
          }
        ],
        "UserData": {
          "Fn::Base64": {
            "Fn::Join": [
              "",
              [
                "#!/bin/bash\n",
                "sudo su\n",
                "su user\n",
                "cd \n",
                "sudo dd if=/dev/zero of=/swapfile bs=1M count=1024\n",
                "sudo mkswap /swapfile\n",
                "sudo swapon /swapfile\n",
                "sudo sed -i \"s/private_ip_address/",
                {
                  "Fn::GetAtt": [
                    "PrivateEc2Instance",
                    "PrivateIp"
                  ]
                },
                "/g\" /etc/nginx/nginx.conf \n",
                "sudo su \n",
                "service nginx start \n"
              ]
            ]
          }
        }
      }
    },
    "ALBTarget": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthCheckIntervalSeconds": "30",
        "HealthCheckPath": "/login",
        "HealthCheckPort": "traffic-port",
        "HealthCheckProtocol": "HTTP",
        "HealthCheckTimeoutSeconds": "5",
        "HealthyThresholdCount": "5",
        "Matcher": {
          "HttpCode": "200"
        },
        "Name": {
          "Ref": "ALBName"
        },
        "Port": "80",
        "Protocol": "HTTP",
        "Targets": [
          {
            "Id": {
              "Ref": "PublicEc2Instance"
            },
            "Port": "80"
          }
        ],
        "UnhealthyThresholdCount": "2",
        "VpcId": {
          "Ref": "VPCID"
        }
      }
    }
  }
}

이것이 기존 ALB의 타겟 그룹에 작성한 EC2를 넣고 nginx와 unicorn의 접속을 가능하게 한 것입니다.

webserver의 UserData 부분에 엉망진창 코드를 넣고 있습니다. 여기서 대체를 하여 연결을 가능하게 하고 있습니다. 또한 스왑 설정도 필요하므로 넣어 두었습니다.

nginx가 시작되지 않았기 때문에 그 설정도 넣어 두었습니다.

끝에



cloudformation으로 템플릿을 만들어 두면 같은 구성의 것을 만들 때 꽤 편해지므로 추천합니다. 또 AMI의 ID 지정으로 그 상태의 것을 사용해 구축할 수 있으므로 이것도 큰 메리트구나-라고 사용해 생각했습니다.

좋은 웹페이지 즐겨찾기