CentOS7上に構築したKubernetesでイメージのダウンロードに失敗する

CentOS7上に構築したKubernetesでイメージのダウンロードに失敗する

Kubernetesで遊ぼうとしてハマった。
まず、こちらの記事を参考にしつつ、AWS上にCentOS7のインスタンスを立ち上げてkubernetesの試験環境を構築した。
Kubernetesでクラスタ環境構築手順(1) – masterの作成 – Qiita

しかし、いざpodをデプロイしようとしたところContainerCreatingのまま進まず、以下のようなエラーが。

[centos@ip-172-20-1-31 ~]$ kubectl run nginx --image=nginx:1.11.3                                                                                                                                                                             
deployment "nginx" created
[centos@ip-172-20-1-31 ~]$ kubectl describe pod nginx                                                                                                                                                                                         
Name:           nginx-752720876-1cgfj
Namespace:      default
Node:           master/172.20.1.31
Start Time:     Mon, 11 Dec 2017 02:08:46 +0000
Labels:         pod-template-hash=752720876
                run=nginx
Status:         Pending
IP:
Controllers:    ReplicaSet/nginx-752720876
Containers:
  nginx:
    Container ID:
    Image:              nginx:1.11.3
    Image ID:
    Port:
    State:              Waiting
      Reason:           ContainerCreating
    Ready:              False
    Restart Count:      0
    Volume Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-kj7lz (ro)
    Environment Variables:      <none>
Conditions:
  Type          Status
  Initialized   True 
  Ready         False 
  PodScheduled  True 
Volumes:
  default-token-kj7lz:
    Type:       Secret (a volume populated by a Secret)
    SecretName: default-token-kj7lz
QoS Class:      BestEffort
Tolerations:    <none>
Events:
  FirstSeen     LastSeen        Count   From                    SubObjectPath   Type            Reason          Message
  ---------     --------        -----   ----                    -------------   --------        ------          -------
  6s            6s              1       {default-scheduler }                    Normal          Scheduled       Successfully assigned nginx-752720876-1cgfj to master
  6s            6s              1       {kubelet master}                        Warning         FailedSync      Error syncing pod, skipping: failed to "StartContainer" for "POD" with ErrImagePull: "image pull failed for registry.access.re
dhat.com/rhel7/pod-infrastructure:latest, this may be because there are no credentials on this request.  details: (open /etc/docker/certs.d/registry.access.redhat.com/redhat-ca.crt: no such file or directory)"

証明書/etc/docker/certs.d/registry.access.redhat.com/redhat-ca.crtが見つからないらしい。
これ自体はシンボリックリンクになっていて、リンク先が存在しない模様。

[centos@ip-172-20-1-252 ~]$ ls -al /etc/docker/certs.d/registry.access.redhat.com/
total 0
drwxr-xr-x. 2 root root 27 Dec 11 00:17 .
drwxr-xr-x. 5 root root 75 Dec 11 00:17 ..
lrwxrwxrwx. 1 root root 27 Dec 11 00:17 redhat-ca.crt -> /etc/rhsm/ca/redhat-uep.pem
[centos@ip-172-20-1-252 ~]$
[centos@ip-172-20-1-252 ~]$ ls /etc/ | grep rhsm
[centos@ip-172-20-1-252 ~]$

色々試してマスターノードにpython-rhsmをインストールして解決した。
証明書もインストールされて、イメージもダウンロードできるようになった。

$ sudo yum install python-rhsm

参考:https://access.redhat.com/ja/node/1379303
(python-rhsmを再インストールするとroot証明書を再インストールできるとの記載。そもそもこのパッケージ入ってなかった)

個人的に遊んでるだけなので、証明書の検証はスキップしても構わないのだけど、insecure的な設定をどこからできるのかがわからなかった。

これでやっとKubernetes触れる。。

続きを読む

絶対的に使った方がいいLogstashのMultiple Pipelinesについて書いてみた

はじめに

おはです!
Logstashのフィルタの中でもGrokが好きなぼくが、Advent Calendar11日目を書かせていただきますー
あ、でも今回は、Grokについては書かないですよ!

じゃあ、何書くの?Grokしか脳のないお前が何を書くのさー
そりゃ、あれだよ!Logstash 6.0がGAされたので、待ちに待ったMultiple Pipelinesについて書くしかないでしょ!

てことで、LogstashのMultiple Pipelinesについて、ゆるーく書いていきます( ゚Д゚)ゞビシッ

構成について

今回テストするにあたって使用した構成は以下です。

  • Amazon Linux AMI 2017.09.1 (HVM)
  • Logstash 6.0
  • logstash-input-s3
  • Elasticsearch 6.0
  • Kibana 6.0
  • X-Pack 6.0

ちなみに、もろもろインストールされている前提で記載しています。
もし、インストールする場合は、以下のインストール手順を参考にして頂ければと思います。

Logstashについて

Logstashは、Elasticsearch社が提供するオープンソースのデータ収集管理ツールです。

LogstashのPipelineは、以下のような流れで処理されます。
例として、インプットデータをApacheのログファイルで、ログファイルに対してフィルタをかけ、Elasticsearchにストアするといった流れにしてます。1

logstash01.png

INPUT

様々なデータソースを取り込みたい!そんな要望に応えるべく用意されたのがLogstash。
Logstashは、様々なデータ形式に対応しています。
例えば、ソフトウェアのログ、サーバのメトリクス、Webアプリケーションのデータや、クラウドサービスなど。

FILTER

INPUTしたデータソースをフィルタで解析し、構造化します。
データソースの変換には、正規表現でデータをパースするためのGrokフィルタや、IPアドレスから地理情報を得るためのGeoIPフィルタなど様々なフィルタライブラリが用意されています。

OUTPUT

データを構造化したのち、任意の出力先にデータをストアします。
Elasticsearch以外の出力先も多数提供されているので、環境に合わせてデータをストアします。

Logastashのディレクトリ構成

上記までが処理の流れで、これらの処理を定義するファイルをLogstashでは準備する必要があります。

以下のようにLogstashのディレクトリは構成されており、conf.d配下に定義ファイル(hoge.confと記載)を配置します。

/etc/logstash
  ├ conf.d
  │ └ hoge.conf
  ├ jvm.options
  ├ log4j2.properties
  └ logstash.yml

hoge.confに様々なデータソースに対して定義します。
例えば、AWSのALBのアクセスログとApacheのアクセスログを定義すると以下のようになります。2

hoge.conf
input {
  s3 {
    tags => "alb"
    bucket => "hoge"
    region => "ap-northeast-1"
    prefix => "hoge/"
    interval => "60"
    sincedb_path => "/var/lib/logstash/sincedb_alb"
  }
  file {
    path => "/etc/logstash/log/httpd_access.log"
    tags => "httpd"
    start_position => "beginning"
    sincedb_path => "/var/lib/logstash/sincedb_httpd"
  }
}
filter {
  if "alb" in [tags] {
    grok {
      patterns_dir => ["/etc/logstash/patterns/cloudfront_patterns"]
      match => { "message" => "%{ALB_ACCESS}" }
      add_field => { "date" => "%{date01} %{time}" }
    }
    date {
      match => [ "date", "yy-MM-dd HH:mm:ss" ]
      locale => "en"
      target => "@timestamp"
    }
    geoip {
      source => "c_ip"
    }
    useragent {
      source => "User_Agent"
      target => "useragent"
    }
    mutate {
      convert => [ "time_taken", "float" ]
      remove_field => [ "message" ]
    }
  else if "httpd" in [tags] {
    grok {
      patterns_dir => ["/etc/logstash/patterns/httpd_patterns"]
      match => { "message" => "%{HTTPD_COMMON_LOG}" }
    }
    geoip {
      source => "clientip"
    }
    date {
      match => [ "date", "dd/MMM/YYYY:HH:mm:ss Z" ]
      locale => "en"
      target => "timestamp"
    }
    mutate {
      remove_field => [ "message", "path", "host", "date" ]
    }
  }
}
output {
  if "alb" in [tags] {
    elasticsearch {
      hosts => [ "localhost:9200" ]
      index => "elb-logs-%{+YYYYMMdd}"
    }
  }
  else if "httpd" in [tags] {
    elasticsearch {
      hosts => [ "localhost:9200" ]
      index => "httpd-logs-%{+YYYYMMdd}"
    }
  }
}

このようにデータソース毎にタグを付与し、if文で判定させるといったかたちになってます。
今回は、データソースが少ないからいいですが、更に増やしていくとなると可読性も悪くなるのと、リソースのハンドリングも難しくなってきます。
また、データソースが増えたり、フィルタ変更などの際には、一つの定義ファイルを更新するため、全体に影響を及ぼす可能性があります。。
なんてこった(´□`;)

Multiple Pipelinesになると何がいいのさ

そこで!Multiple Pipelinesの出番なのです!
hoge.confに対して複数のデータソースを組み込んでいたものを、分割することができちゃうのですー
また、リソースの割り当てとして、Worker数などもPipeline毎に割り当てることができるのです!歓喜!!

ここからは、Multiple Pipelinesをどのように使うのかを書いてきます。

Multiple Pipelinesを試してみる。

Logstashのディレクトリ構成は変わらないのですが、新しくpipelines.ymlという定義ファイルを作成します。
また、先ほどのhoge.confをデータソース毎にファイルを以下のように分けます。

  • alb.cfg
  • httpd.cfg

alb.cfgの定義は以下です。

alb.cfg
input {
  s3 {
    bucket => "hoge"
    region => "ap-northeast-1"
    prefix => "hoge/"
    interval => "60"
    sincedb_path => "/var/lib/logstash/sincedb_alb"
  }
}
filter {
  grok {
    patterns_dir => ["/etc/logstash/patterns/cloudfront_patterns"]
    match => { "message" => "%{ALB_ACCESS}" }
    add_field => { "date" => "%{date01} %{time}" }
  }
  date {
    match => [ "date", "yy-MM-dd HH:mm:ss" ]
    locale => "en"
    target => "@timestamp"
  }
  geoip {
    source => "c_ip"
  }
  useragent {
    source => "User_Agent"
    target => "useragent"
  }
  mutate {
    convert => [ "time_taken", "float" ]
    remove_field => [ "message" ]
  }
 }
output {
  elasticsearch {
    hosts => [ "localhost:9200" ]
    index => "elb-logs-%{+YYYYMMdd}"
  }
}

httpd.cfgの定義は以下です。

httpd.cfg
input {
  file {
    path => "/etc/logstash/log/httpd_access.log"
    start_position => "beginning"
    sincedb_path => "/var/lib/logstash/sincedb_httpd"
  }
}
filter {
  grok {
    patterns_dir => ["/etc/logstash/patterns/httpd_patterns"]
    match => { "message" => "%{HTTPD_COMMON_LOG}" }
  }
  geoip {
    source => "clientip"
  }
  date {
    match => [ "date", "dd/MMM/YYYY:HH:mm:ss Z" ]
    locale => "en"
    target => "timestamp"
  }
  mutate {
    remove_field => [ "message", "path", "host", "date" ]
  }
}
output {
  elasticsearch {
    hosts => [ "localhost:9200" ]
    index => "httpd-%{+YYYYMMdd}"
  }
}

ディレクトリ構成は以下になります。

/etc/logstash
  ├ logstash.yml
  ├ conf.d
  │ └ alb.cfg
  │ └ httpd.cfg
  ├ jvm.options
  ├ log4j2.properties
  └ pipelines.yml

ここでやっと登場するpipelines.ymlの定義は以下です。

pipelines.yml
- pipeline.id: alb
  pipeline.batch.size: 125
  path.config: "/etc/logstash/conf.d/alb.cfg"
  pipeline.workers: 1
- pipeline.id: httpd
  pipeline.batch.size: 125
  path.config: "/etc/logstash/conf.d/httpd.cfg"
  pipeline.workers: 1

めっちゃシンプルですね。
呼び出すファイルパスの指定とパラメータの定義を記載するだけです。
データソース単位でpipeline.workersを割り当てられることになったので、柔軟にコアの配分ができるようになりました。
詳細の設定については、settings fileを確認してもらえればと思います。

動かすよ!

今回、サービス起動を前提としているため、pipelines.ymlを読み込ませるには、logstash.confのpath.configをコメントアウトする必要があります。
(ここでハマりました。。公式サイトにサービス起動について書かれているところがみつからず。。)

logstash.conf
# path.config: /etc/logstash/conf.d/*.conf

てことで、起動しますー
これでデータソース単位で動かすことができます。

$ initctl start logstash
logstash start/running, process 3121

おまけ

今回説明していないですが、X-Packを導入することで、MonitoringでPipelineの状況をみることができます。
データの流れを把握したり、分岐などのロジックが有効に働いてるかなども確認することが可能です。

先ほど、alb.cfgを取り込んだ際のPipelineは以下の様に表示されます。

logstash03.png

さいごに

いかがでしたか?
Multiple Pipelinesは、常にデータを取り込んでいるLogstashを支えるための根幹たる機能ではないでしょうか?
ぜひぜひ、みなさんもMultiple Pipelinesを使って、よりよいLogstsah Lifeを送って頂ければとヽ(*゚д゚)ノ

それでは、Advent Calendar11日目を終わりますー
明日の12日目は、”Elasticsearch v1.7 -> v2.4 -> v5.5 と段階的にバージョンアップした話し”ですね!
楽しみだすー


  1. Elasticsearch社の公開されているグローバルIPを利用しています 

  2. 指定しているパスは、環境に合わせて指定してください 

続きを読む

Docker + Nginx + Let’s EncryptでHTTPS対応のプロキシサーバーを構築する

Docker上にNginxコンテナをプロキシサーバーとして構築し、Let’s EncryptでHTTPS対応しました。構築にあたって かなり苦戦した ので、そのノウハウを記事としてまとめました。

「Nginx」とは

Apacheなどの従来のWebサーバーは、クライアントの数が多くなるとサーバーがパンクする 「C10K問題(クライアント1万台問題)」 を抱えていました。「Nginx」はこの問題を解決するために誕生した、静的コンテンツを高速配信するWebサーバーです。2017年10月現在、そのシェアは Apacheとほぼ同等 となっています。

wpid-wss-share13.png

Webサーバー シェア
Micosoft IIS 49.44%
Apache 18.78%
Nginx 18.40%

「Let’s Encrypt」とは

「Let’s Encrypt」は すべてのWebサーバへの接続を暗号化する ことを目指し、SSL/TLSサーバ証明書を 無料 で発行する認証局(CA)です。シスコ、Akamai、電子フロンティア財団、モジラ財団などの大手企業・団体がスポンサーとして支援しています。


本稿が目指すシステム構成

本稿ではAmazon EC2、Dockerコンテナを使用して以下のようなシステムを構築することを目標とします。

DockerでNgixのプロキシサーバーを構築する.png

前提条件

  • 独自ドメインを取得していること(本稿で使用するドメインはexample.comとします)
  • IPv4パブリックIP(Elastic IP)がEC2インスタンスに設定されていること
  • EC2インスタンスにDocker、docker-composeがインストールされていること

事前に準備すること

DockerでHTTPS対応のプロキシサーバーを構築するにあたり、事前に以下の設定をしておく必要があります。

  • EC2のインバウンドルールで443ポートを開放する
  • DNSのAレコードを設定する
  • プロキシ用のネットワークを構築する

EC2のインバウンドルールで443ポートを開放する

インバウンドルールを以下のように設定し、443ポートを外部へ公開します。

タイプ プロトコル ポート範囲 ソース
HTTPS TCP 443 0.0.0.0/0
HTTPS TCP 443 ::/0

DNSのAレコードを設定する

DNSの設定方法は利用しているドメイン取得サービスによって異なります。例えばバリュードメインの場合、DNSの設定方法は「DNS情報・URL転送の設定 | VALUE-DOMAIN ユーザーガイド」に記載されています。

DNSのAレコードを以下のように設定します。xx.xx.xx.xxにはEC2インスタンスに割り当てられているIPv4パブリックIPを設定します。

a @ xx.xx.xx.xx
a www xx.xx.xx.xx

上記設定は以下を意味します。

  • example.com(サブドメイン無し)をIPアドレスxx.xx.xx.xxにポイントする
  • www.example.com をIPアドレスxx.xx.xx.xxにポイントする

プロキシ用のネットワークを構築する

プロキシサーバーとWebサーバー間のネットワークは外部との通信を行う必要がありません。そこで
プロキシサーバーとWebサーバー間の 内部ネットワーク を構築するため、EC2のインスタンスにログインし、以下のコマンドを入力します。

$ docker network create --internal sample_proxy_nw

上記コマンドは以下を意味します。

  • --internal: ネットワーク外との通信が行えないネットワークを作成します。
  • sample_proxy_nw: 任意のネットワーク名です。

以下のコマンドを入力し、ネットワークの設定情報がコンソールに出力されていることを確認しましょう。

$ docker network inspect sample_proxy_nw

Dockerコンテナの定義ファイルを作成する

事前準備が完了したら、Dockerコンテナの定義ファイルを作成しましょう。本稿におけるディレクトリ構成は以下のとおりです。

/path/to/dir/

.
├── docker-compose.yml // プロキシサーバーとWebサーバーのコンテナを定義するファイル
└── proxy
    ├── default.conf // プロキシサーバー上にあるNginxのデフォルト定義ファイル
    ├── Dockerfile // プロキシサーバーのイメージを構築するためのファイル
    └── entrypoint.sh // プロキシサーバーにSSL証明書を取得するためのファイル

以下では、各ファイルの内容を解説します。

./docker-compose.yml

docker-compose.ymlでは、以下のコンテナを定義しています。

  • proxy: プロキシサーバー(Nginxベース)
  • web1: Webサーバー(httpdベース)
  • web2: Webサーバー(httpdベース)
version: '3'
services:
  proxy:
    build: ./proxy
    tty: true
    image: sample_proxy
    container_name: sample_proxy
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    ports:
      - "443:443"
    volumes:
      - '/srv/letsencrypt:/etc/letsencrypt'
    networks:
      - default
      - sample_proxy_nw
    depends_on:
      - "web1"
      - "web2"
    command: ["wait-for-it.sh", "sample_web1:80", "--", "./wait-for-it.sh", "sample_web2:80"]
  web1:
    image: httpd
    container_name: sample_web1
    tty: true
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - sample_proxy_nw
  web2:
    image: httpd
    container_name: sample_web2
    tty: true
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - sample_proxy_nw
networks:
  proxy_nw:
    external: true

上記コマンドは以下を意味します。

  • サービスproxyports: 外部からのHTTPSアクセスとproxyサーバーの内部ポートを疎通させるため、443:443を定義します。
  • サービスproxyvolumes: /srv/letsencrypt:/etc/letsencryptを定義します。/etc/letsencryptLet’s Encryptで取得した証明書が生成されるディレクトリ です。
  • networks: 上述の説明で生成したsample_proxy_nwを各サービス(proxy, web1, web2)に定義します。
  • depends_on: コンテナの起動順序を制御するオプションです。 Nginxのproxy_passに設定されているWebサーバーが起動していない状態でプロキシサーバーが起動した場合にエラーとなる ため、web1, web2を設定します。

./proxy/default.conf

./proxy/default.confはNginxのデフォルト定義ファイル(/etc/nginx/conf.d/default.conf)を書き換えるためのファイルです。

server{

    server_name example.com www.example.com;

    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        proxy_pass    http://sample_web1/;
    }

    location /example/ {
        proxy_pass    http://sample_web2/;
    }

}

上記設定は以下を意味します。

  • server_name: ユーザーから要求されるHTTPリクエストのヘッダに含まれるHostフィールドとserver_nameが一致した場合、該当するサーバ設定を採用します。Nginxではキャッチオールサーバーとして_を定義することもできますが、 certbot-autoがサーバー情報を正しく取得することができない ため、上記のようにドメイン名を入力します。
  • location: ルートディレクトリ(example.com/)とサブディレクトリ(example.com/example/)にアクセスした際の振り分け先URIを設定します。proxy_passには、http://[コンテナ名]/を設定します。コンテナ名はdocker-compose.ymlのcontainer_nameで設定した名前となります。
    また、http://sample_web1/のように 末尾に/を入れる ことに注意しましょう。例えばlocation /example/において、プロキシパスの末尾に/が含まれていない(http://sample_web2)場合、振り分け先は http://sample_web2/example/となってしまいます。

./proxy/Dockerfile

FROM nginx
COPY default.conf /etc/nginx/conf.d/default.conf
RUN apt-get update && apt-get install -y \
        wget && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh /usr/local/bin/wait-for-it.sh
RUN chmod +x /usr/local/bin/wait-for-it.sh
ADD https://dl.eff.org/certbot-auto /usr/local/bin/certbot-auto
RUN chmod a+x /usr/local/bin/certbot-auto
RUN certbot-auto --os-packages-only -n
COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

上記設定は以下を意味します。

  • ADD https://dl.eff.org/certbot-auto /usr/local/bin/certbot-auto: Let’s Encryptが発行するSSL/TLSサーバ証明書を自動で取得・更新するツール「 certbot-auto 」をダウンロードします。

./proxy/entrypoint.sh

#!/bin/bash
certbot-auto --nginx -d example.com -d www.example.com -m your-account@gmail.com --agree-tos -n
certbot-auto renew
/bin/bash

上記設定は以下を意味します。

  • --nginx: プロキシサーバーにNginxを使用する場合のオプションです。default.confの設定を自動的に書き換えます。(2017年12月現在、アルファ版のプラグイン)
  • -d example.com -d www.example.com: SSL/TLSサーバ証明書の取得を申請するドメイン名を指定します。
  • -m your-account@gmail.com: アカウントの登録や回復などに使用する電子メールアドレスを指定します。
  • --agree-tos: Let’s Encryptの利用規約に同意します。
  • -n: インタラクティブ設定をオフにします。
  • ./certbot-auto renew: 3ヶ月で失効する SSL/TLSサーバ証明書を自動で更新します。

以下のコマンドを入力してentrypoint.shに 実行権限を付与する ことを忘れないようにしましょう。

$ chmod +x entrypoint.sh

Dockerコンテナを起動する

それでは以下のコマンドを入力してDockerコンテナを起動しましょう。

docker-compose up -d

しばらく時間をおいてから、以下のコマンドを入力します。

docker-compose logs

以下のように出力されていれば成功です。

-------------------------------------------------------------------------------
Congratulations! You have successfully enabled https://example,com and
https://www.example.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=example.com
https://www.ssllabs.com/ssltest/analyze.html?d=www.example.com
-------------------------------------------------------------------------------

HTTPSでアクセスする

ブラウザを起動し、実際に以下のURLにアクセスしてみましょう。

Chromeブラウザの場合はデベロッパーツール > Security > View certificateからSSL/TLSサーバ証明書を確認することができます。

「発行元: Let’s Encrypt Authority X3」となっているはずです。

続きを読む

consul-template & supervisorでプロセスの可視化

こちらはフロムスクラッチ Advent Calendar 2017の9日目の記事です。

はじめに

ポプテピピック

もうすぐ、ポプテピピック始まりますね。
どうも、jkkitakitaです。

概要

掲題通り、consul + supervisordで
プロセス監視、管理に関して、可視化した話します。

きっかけ

どうしても、新規サービス構築や保守運用しはじめて
色々なバッチ処理等のdaemon・プロセスが数十個とかに増えてくると
↓のような悩みがでてくるのではないでしょうか。

  1. 一時的に、daemonをstopしたい
  2. daemonがゾンビになってて、再起動したい
  3. daemonが起動しなかった場合の、daemonのログを見る
  4. daemonが動いているのかどうか、ぱっとよくわからない。
  5. ぱっとわからないから、なんか不安。 :scream:

個人的には
5.は、結構感じます。笑
安心したいです。笑

ツールとその特徴・選定理由

簡単に本記事で取り扱うツールのバージョン・特徴と
今回ツールを選んだ選定理由を記載します。

ツール 特徴 選定理由
supervisor
v3.3.1
1. プロセス管理ツール
2. 2004年から使われており、他でよく使われているdaemon化ツール(upstart, systemd)と比較して、十分枯れている。
3. 柔軟な「プロセス管理」ができる。
4. APIを利用して、プロセスのstart/stop/restart…などが他から実行できる。
1.今までupstartを使っていたが、柔軟な「プロセス管理」ができなかったため。

※ upstartは「プロセス管理」よりかは、「起動設定」の印象。

consul
v1.0.1
1. サービスディスカバリ、ヘルスチェック、KVS etc…
2. その他特徴は、他の記事参照。
https://www.slideshare.net/ssuser07ce9c/consul-58146464
1. AutoScalingするサーバー・サービスの死活監視

2. 単純に使ってみたかった。(笑)

3. 本投稿のconsul-templateを利用に必要だったから(サービスディスカバリ)

consul-template
v0.19.4
1. サーバー上で、consul-templateのdaemonを起動して使用
2. consulから値を取得して、設定ファイルの書き換え等を行うためのサービス
ex.) AutoScalingGroupでスケールアウトされたwebサーバーのnginx.confの自動書き換え
1. ansibleのようなpush型の構成管理ツールだと、AutoScalingGroupを使った場合のサーバー内の設定ファイルの書き換えが難しい。

2. user-data/cloud-initを使えば実現できるが、コード/管理が煩雑になる。保守性が低い。

cesi
versionなし
1. supervisordのダッシュボードツール
2. supervisordで管理されているdaemonを画面から一限管理できる
3. 画面から、start/stop/restartができる
4. 簡易的なユーザー管理による権限制御ができる
1. とにかく画面がほしかった。

2. 自前でも作れるが、公式ドキュメントに載っていたから

3. 他にもいくつかOSSダッシュボードあったが、一番UIがすっきりしていたから。(笑)

実際にやってみた

上記ツールを使って
daemonを可視化するために必要な設定をしてみました。
本記事は、全て、ansibleを使って設定していて
基本的なroleは
ansible-galaxyで、juwaiさんのroleを
お借りしています。
https://galaxy.ansible.com/list#/roles?page=1&page_size=10&tags=amazon&users=juwai&autocomplete=consul

supervisor

クライアント側(実際に管理したいdaemonが起動するサーバー)

supervisord.conf
; Sample supervisor config file.
;
; For more information on the config file, please see:
; http://supervisord.org/configuration.html
;
; Notes:
;  - Shell expansion ("~" or "$HOME") is not supported.  Environment
;    variables can be expanded using this syntax: "%(ENV_HOME)s".
;  - Comments must have a leading space: "a=b ;comment" not "a=b;comment".

[unix_http_server]
file=/tmp/supervisor.sock   ; (the path to the socket file)
;chmod=0700                 ; socket file mode (default 0700)
;chown=nobody:nogroup       ; socket file uid:gid owner
;username=user              ; (default is no username (open server))
;password=123               ; (default is no password (open server))

[inet_http_server]         ; inet (TCP) server disabled by default
port=0.0.0.0:9001        ; (ip_address:port specifier, *:port for all iface)
username=hogehoge              ; (default is no username (open server))
password=fugafuga               ; (default is no password (open server))
;セキュリティ観点から、ここのportは絞る必要有。

[supervisord]
logfile=/tmp/supervisord.log        ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB               ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10                  ; (num of main logfile rotation backups;default 10)
loglevel=info                       ; (log level;default info; others: debug,warn,trace)
pidfile=/tmp/supervisord.pid        ; (supervisord pidfile;default supervisord.pid)
nodaemon=false ; (start in foreground if true;default false)
minfds=1024                         ; (min. avail startup file descriptors;default 1024)
minprocs=200                        ; (min. avail process descriptors;default 200)

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket

[include]
files=/etc/supervisor.d/*.conf

/etc/supervisor.d/配下に
起動するdaemonを設定します。

daemon.conf
[group:daemon]
programs=<daemon-name>
priority=999

[program:<daemon-name>]
command=sudo -u ec2-user -i /bin/bash -c 'cd /opt/<service> && <実行コマンド>'
user=ec2-user
group=ec2-user
directory=/opt/<service>
autostart=true
autorestart=true
redirect_stdout=true
redirect_stderr=true
stopasgroup=true
stopsignal=QUIT
stdout_logfile=/var/log/<service>/daemon.stdout.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=10
stderr_logfile=/var/log/<service>/daemon.stderr.log
stderr_logfile_maxbytes=20MB
stderr_logfile_backups=10


[eventlistener:slack_notifier]
command=/usr/bin/process_state_event_listener.py
events=PROCESS_STATE
redirect_stderr=false
stopasgroup=true
stopsignal=QUIT
stdout_logfile=/var/log/<service>/event_listener.stdout.log
stdout_logfile_maxbytes=2MB
stdout_logfile_backups=10
stderr_logfile=/var/log/<service>/event_listener.stderr.log
stderr_logfile_maxbytes=2MB
stderr_logfile_backups=10
environment=SLACK_WEB_HOOK_URL="xxxxxxx"

eventlistener:slack_notifierは、下記投稿を参考に作成。
https://qiita.com/imunew/items/465521e30fae238cf7d0

[root@test02 ~]# supervisorctl status
daemon:<daemon-name>              RUNNING   pid 31513, uptime 13:19:20
slack_notifier                    RUNNING   pid 31511, uptime 13:19:20

server側(daemonの管理画面を表示するwebサーバー)

supervisord.conf
クライアント側と同様

consul

server側

[root@server01 consul_1.0.1]# pwd
/home/consul/consul_1.0.1

[root@server01 consul_1.0.1]# ll
total 16
drwxr-xr-x 2 consul consul 4096 Dec  3 04:49 bin
drwxr-xr-x 2 consul consul 4096 Dec  3 06:06 consul.d
drwxr-xr-x 4 consul consul 4096 Dec  3 04:50 data
drwxr-xr-x 2 consul consul 4096 Dec  3 04:50 logs

[root@server01 consul.d]# pwd
/home/consul/consul_1.0.1/consul.d

[root@server01 consul.d]# ll
total 16
-rw-r--r-- 1 consul consul 382 Dec  3 06:06 common.json
-rw-r--r-- 1 consul consul 117 Dec  3 04:49 connection.json
-rw-r--r-- 1 consul consul  84 Dec  3 04:49 server.json
-rw-r--r-- 1 consul consul 259 Dec  3 04:49 supervisord.json
/home/consul/consul_1.0.1/consul.d/common.json
{
  "datacenter": "dc1",
  "data_dir": "/home/consul/consul_1.0.1/data",
  "encrypt": "xxxxxxxxxxxxxxx", // consul keygenで発行した値を使用。
  "log_level": "info",
  "enable_syslog": true,
  "enable_debug": true,
  "node_name": "server01",
  "leave_on_terminate": false,
  "skip_leave_on_interrupt": true,
  "enable_script_checks": true, // ここtrueでないと、check script実行できない
  "rejoin_after_leave": true
}
/home/consul/consul_1.0.1/consul.d/connection.json
{
  "client_addr": "0.0.0.0",
  "bind_addr": "xxx.xxx.xxx.xxx", // 自身のprivate ip
  "ports": {
    "http": 8500,
    "server": 8300
  }
}
/home/consul/consul_1.0.1/consul.d/server.json
{
  "server": true, // server側なので、true
  "server_name": "server01",
  "bootstrap_expect": 1 // とりあえず、serverは1台クラスタにした
}
/home/consul/consul_1.0.1/consul.d/supervisord.json
{
  "services": [
    {
      "id": "supervisord-server01",
      "name": "supervisord",
      "tags" : [ "common" ],
      "checks": [{
        "script": "/etc/init.d/supervisord status | grep running",
        "interval": "10s"
      }]
    }
  ]
}

consul自体もsupervisordで起動します。

/etc/supervisor.d/consul.conf
[program:consul]
command=/home/consul/consul_1.0.1/bin/consul agent -config-dir=/home/consul/consul_1.0.1/consul.d -ui // -uiをつけて、uiも含めて起動。
user=consul
group=consul
autostart=true
autorestart=true
redirect_stdout=true
redirect_stderr=true
stdout_logfile=/home/consul/consul_1.0.1/logs/consul.stdout.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=10
stderr_logfile=/home/consul/consul_1.0.1/logs/consul.stderr.log
stderr_logfile_maxbytes=20MB
stderr_logfile_backups=10

agent側(管理したいdaemonが起動するサーバー側)

/home/consul/consul_1.0.1/consul.d/common.json
{
  "datacenter": "dc1",
  "data_dir": "/home/consul/consul_1.0.1/data",
  "encrypt": "xxxxxxxxxxxxxxx", // server側と同じencrypt
  "log_level": "info",
  "enable_syslog": true,
  "enable_debug": true,
  "node_name": "agent01",
  "leave_on_terminate": false,
  "skip_leave_on_interrupt": true,
  "enable_script_checks": true,
  "rejoin_after_leave": true,
  "retry_join": ["provider=aws tag_key=Service tag_value=consulserver region=us-west-2 access_key_id=xxxxxxxxxxxxxx secret_access_key=xxxxxxxxxxxxxxx"
  // retry joinでserver側と接続。serverのcluster化も考慮して、provider=awsで、tag_keyを指定。
]
  }
/home/consul/consul_1.0.1/consul.d/connection.json
{
  "client_addr": "0.0.0.0",
  "bind_addr": "xxx.xxx.xxx.xxx", // 自身のprivate ip
  "ports": {
    "http": 8500,
    "server": 8300
  }
}
/home/consul/consul_1.0.1/consul.d/daemon.json
{
  "services": [
        {
      "id": "<daemon-name>-agent01",
      "name": "<daemon-name>",
      "tags" : [ "daemon" ],
      "checks": [{
        "script": "supervisorctl status daemon:<daemon-name> | grep RUNNING",
        "interval": "10s"
      }]
    }
  ]
}
/home/consul/consul_1.0.1/consul.d/supervisord.json
{
  "services": [
    {
      "id": "supervisord-agent01",
      "name": "supervisord",
      "tags" : [ "common" ],
      "checks": [{
        "script": "/etc/init.d/supervisord status | grep running",
        "interval": "10s"
      }]
    }
  ]
}

agent側もsupervisordで管理

/etc/supervisor.d/consul.conf
[program:consul]
command=/home/consul/consul_1.0.1/bin/consul agent -config-dir=/home/consul/consul_1.0.1/consul.d // -uiは不要
user=consul
group=consul
autostart=true
autorestart=true
redirect_stdout=true
redirect_stderr=true
stdout_logfile=/home/consul/consul_1.0.1/logs/consul.stdout.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=10
stderr_logfile=/home/consul/consul_1.0.1/logs/consul.stderr.log
stderr_logfile_maxbytes=20MB
stderr_logfile_backups=10

cesi

image2.png

こちらのrepoから拝借させていただきました :bow:
基本的な設定は、README.mdに記載されている通り、セットアップします。

/etc/cesi.conf
[node:server01]
username = hogehoge
password = fugafuga
host = xxx.xxx.xxx.xxx // 対象nodeのprivate ip
port = 9001

[node:test01]
username = hogehoge
password = fugafuga
host = xxx.xxx.xxx.xxx // 対象nodeのprivate ip
port = 9001

[cesi]
database = /path/to/cesi-userinfo.db
activity_log = /path/to/cesi-activity.log
host = 0.0.0.0

(ansibleのroleにもしておく。)
cesiのコマンドも簡単にsupervisordで管理する様に設定します。

/etc/supervisor.d/cesi.conf
[program:cesi]
command=python /var/www/cesi/web.py
user=root
group=root
autostart=true
autorestart=true
redirect_stdout=true
redirect_stderr=true
stopasgroup=true
stopsignal=QUIT
stdout_logfile=/root/cesi.stdout.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=10
stderr_logfile=/root/cesi.stderr.log
stderr_logfile_maxbytes=20MB
stderr_logfile_backups=10

スクリーンショット 2017-12-10 1.51.12.png

うん、いい感じに画面でてますね。
ただ、この画面の欠点としてnodeが増えるたびに、
都度、 /etc/cesi.confを書き換えては
webサーバーを再起動しなければならない欠点がありました。
なので
今生きているサーバーは何があるのかを把握する必要がありました。
 → まさにサービスディスカバリ。
そこで、設定ファイルの書き方もある一定柔軟にテンプレート化できる
consul-tamplteの登場です。

consul-template

ここも同様にして、ansibleで導入します。
https://github.com/juwai/ansible-role-consul-template
あとは、いい感じに公式ドキュメントをみながら、templateを書けばok。

[root@agent01 config]# ll
total 8
-rwxr-xr-x 1 root   root    220 Dec  4 05:16 consul-template.cfg
/home/consul/consul-template/config/consul-template.cfg
consul = "127.0.0.1:8500"
wait = "10s"

template {
  source = "/home/consul/consul-template/templates/cesi.conf.tmpl"
  destination = "/etc/cesi.conf"
  command = "supervisorctl restart cesi"
  command_timeout = "60s"
}
/home/consul/consul-template/templates/cesi.conf.tmpl
{{range service "supervisord"}}
[node:{{.Node}}]
username = hogehoge
password = fugafuga
host = {{.Address}}
port = 9001

{{end}}

[cesi]
database = /path/to/cesi-userinfo.db
activity_log = /path/to/cesi-activity.log
host = 0.0.0.0

上記のように、consul-tamplateの中で
{{.Node}}という値を入れていれば
consulでsupervisordのnode追加・更新をトリガーとして
consul-templateが起動し

  1. /etc/cesi.confの設定ファイルの更新
  2. cesiのwebserverの再起動

が実現でき、ダッシュボードにて、supervisordが、管理できるようになります。

また
consul-templateは、daemonとして起動しておくものなので
consul-templateもまた、supervisordで管理します。

/etc/supervisor.d/consul-template.conf
[program:consul-template]
command=/home/consul/consul-template/bin/consul-template -config /home/consul/consul-template/config/consul-template.cfg
user=root
group=root
autostart=true
autorestart=true
redirect_stdout=true
redirect_stderr=true
stdout_logfile=/home/consul/consul-template/logs/stdout.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=10
stderr_logfile=/home/consul/consul-template/logs/stderr.log
stderr_logfile_maxbytes=20MB
stderr_logfile_backups=10

早速、実際サーバーを立ててみると…

スクリーンショット 2017-12-10 1.48.57.png

うん、いい感じにサーバーの台数が8->9台に増えてますね。
感覚的にも、増えるとほぼ同時に画面側も更新されてるので
結構いい感じです。(減らした時も同じ感じでした。)

めでたしめでたし。

やってみて、感じたこと

Good

  1. 各サーバーのプロセスの可視化できると確かに「なんか」安心する。
  2. サーバー入らずに、プロセスのstart/stop/restartできるのは、運用的にもセキュリティ的にも楽。
  3. supervisordは、探しても記事とかあまりない?気がするが、本当にプロセスを「管理」するのであれば、感覚的には、まぁまぁ使えるんじゃないかと感じた。
  4. consul-templateの柔軟性が高く、consulの設計次第でなんでもできる感じがよい。
  5. 遊び半分で作ってみたが、思ったより評判はよさげだった笑

Not Good

  1. supervisord自体のプロセス監視がうまいことできていない。
  2. まだまだsupervisordの設定周りを理解しきれていない。。。
     ※ ネットワーク/権限/セキュリティ周りのところが今後の課題。。usernameとかなんか一致してなくても、取れちゃってる・・・?笑
  3. consulもまだまだ使えていない。。。
  4. cesiもいい感じだが、挙動不審なところが若干ある。笑
    ※ 他のダッシュボードもレガシー感がすごくて、あまり、、、supervisordのもういい感じの画面がほしいな。
    http://supervisord.org/plugins.html#dashboards-and-tools-for-multiple-supervisor-instances

さいごに

プロセスって結構気づいたら落ちている気がしますが
(「いや、お前のツールに対する理解が浅いだけだろ!」っていうツッコミはやめてください笑)

単純にダッシュボードという形で
「可視化」して、人の目との接触回数が増えるだけでも
保守/運用性は高まる気がするので
やっぱりダッシュボード的なのはいいなと思いました^^

p.s.
色々と設定ファイルを記載していますが
「ん?ここおかしくないか?」というところがあれば
ぜひ、コメントお願いいたします :bow:

続きを読む

CloudFormationを使って一撃で作るAssumeRoleによる強い💪権限管理

インフラエンジニアのみつの(@kotatsu360)です。
この記事はVASILY Advent Calendar 201710日目の記事です。

この記事では、AWSのIAMでAssumeRoleする権限周りを一撃でつくるCloudFormationテンプレートをまとめます。ご査収ください。

CloudFormationが作る環境のイメージ
image.png

このドキュメントの目的

IAMは調べれば調べるほど情報が断片的になる。あとJSON書くの面倒(一一 )
CloudFormationならYAMLで書けるし再現性がある。テンプレートにまとめようヽ(゚∀゚)ノ パッ☆

AssumeRoleとは

  1. 役割ごとに必要な権限を持ったIAMロールを作っておく

    • 開発
    • 運用
    • 請求
    • 監査
    • etc…
  2. IAMロールごとに「自分の権限を使うことを許可する」対象を設定しておく
    • 開発用のロールはエンジニアAとBとCと…
    • IAMロール「力がほしい時はいつでも我が名を呼ぶが良い」
  3. つかう
    • 変身
    • 普段はブラックRX。たまにはバイオライダー。

メリット

  • 権限をロールという単位で整理できる

    • 個人ごとに細かく設定するとスパゲッティになりがち
  • 場面場面で必要な権限だけが使える
    • 「開発時に誤って本番構成を変更してしまって泣く」ということが減る
  • 権限の確認が楽
    • 個人ごとに設定すると、権限付与者(おそらく強い権限持ち)と非付与者の差異が生まれる
    • AssumeRoleであれば、そのロールにスイッチして自分で権限を確認できる

デメリット

  • めんどう

    • 「rootアカウントでいいじゃん」と言われたらそこまで・・・
    • アカウント管理をやりたい時はとてもオススメ

なお、後から入れるのはカロリーを消費するので導入は早い方が良いと思います。
また、CloudFormationを使うとコード管理できるので、かなり楽になります。

CloudFormationテンプレートで実行されること

  • IAMユーザの作成

    • ユーザは4人
    • 開発者
    • 開発者&本番運用者
    • 開発者&本番運用者
    • 請求管理者
    • 初ログイン時にパスワード変更画面を出す
  • IAMグループの作成
    • 全員用のグループ
    • 本番運用者用のグループ
  • IAMロールの作成
    • 開発者用の権限を管理するロール
    • 本番運用者用の権限を管理するロール
    • 請求管理者用の権限を管理するロール
  • ユーザをグループに追加

グループとロールの住み分けについては後で触れます。

CloudFormationテンプレート

初期パスワードが小文字と記号になっているので、アカウントのパスワードポリシーによっては作成に失敗します。

AWSTemplateFormatVersion: 2010-09-09
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: 'IAM User Name'
        Parameters:
          - Dev1
          - Prod1
          - Prod2
          - Billing1
Parameters:
  Dev1:
    Type: String
    Default: 'kotatsu360a'
  Prod1:
    Type: String
    Default: 'kotatsu360b'
  Prod2:
    Type: String
    Default: 'kotatsu360c'
  Billing1:
    Type: String
    Default: 'kotatsu360d'
Resources:
  # ユーザ全員が所属するグループ
  IAMGroupAllMember:
    Type: 'AWS::IAM::Group'
    Properties:
      Policies:
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action:
                  - 'iam:List*'
                Resource: '*'
              - Effect: 'Allow'
                Action:
                  - 'iam:GetLoginProfile'
                  - 'iam:ChangePassword'
                  - 'iam:CreateVirtualMFADevice'
                  - 'iam:DeleteVirtualMFADevice'
                  - 'iam:EnableMFADevice'
                Resource: 
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:mfa/${!aws:username}'
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:user/${!aws:username}'
             - Effect: 'Allow'
                Action:
                  - 'am:DeactivateMFADevice'
                Resource:
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:mfa/${!aws:username}'
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:user/${!aws:username}'
                Condition:
                  Bool:
                    aws:MultiFactorAuthPresent: true
          PolicyName: 'mfa-attach'

  # 本番運用者が所属するグループ
  IAMGroupProductionOperator:
    Type: 'AWS::IAM::Group'
    Properties:
      Policies:
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action:
                  - 'opsworks:UpdateMyUserProfile'
                  - 'opsworks:Describe*'
                Resource: '*'
          PolicyName: 'change-my-ssh-key'

  # [NOTE] 開発者と請求管理者については個人宛のルールが必要ないのでグループを作らない

  # 開発者の権限を管理するロール
  IAMRoleDeveloper:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'developer'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !GetAtt IAMUserDev1.Arn
                - !GetAtt IAMUserProd1.Arn
                - !GetAtt IAMUserProd2.Arn
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/ReadOnlyAccess'

  # 本番運用者の権限を管理するロール
  IAMRoleProductionOperator:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'production-operator'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !GetAtt IAMUserProd1.Arn
                - !GetAtt IAMUserProd2.Arn
            Action: 'sts:AssumeRole'
            Condition:
              Bool:
                aws:MultiFactorAuthPresent: true
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/ReadOnlyAccess'
        - 'arn:aws:iam::aws:policy/AmazonS3FullAccess'

  # 請求管理者の権限を管理するロール
  IAMRoleBillingOwner:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: 'billing-owner'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !GetAtt IAMUserBilling1.Arn
            Action: 'sts:AssumeRole'
            Condition:
              Bool:
                aws:MultiFactorAuthPresent: true
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/job-function/Billing'

  IAMUserDev1:
    Type: 'AWS::IAM::User'
    Properties:
      LoginProfile:
        Password: 'i_love_whisky'
        PasswordResetRequired: true
      UserName: !Ref Dev1

  IAMUserProd1:
    Type: 'AWS::IAM::User'
    Properties:
      LoginProfile:
        Password: 'i_love_cigar'
        PasswordResetRequired: true
      UserName: !Ref Prod1

  IAMUserProd2:
    Type: 'AWS::IAM::User'
    Properties:
      LoginProfile:
        Password: 'i_love_sleeping'
        PasswordResetRequired: true
      UserName: !Ref Prod2

  IAMUserBilling1:
    Type: 'AWS::IAM::User'
    Properties:
      LoginProfile:
        Password: 'i_love_money'
        PasswordResetRequired: true
      UserName: !Ref Billing1

  IAMUserToGroupAdditionAllMember:
    Type: 'AWS::IAM::UserToGroupAddition'
    Properties:
      GroupName: !Ref IAMGroupAllMember
      Users:
        - !Ref IAMUserDev1
        - !Ref IAMUserProd1
        - !Ref IAMUserProd2
        - !Ref IAMUserBilling1

  IAMUserToGroupAdditionProductionOperator:
    Type: 'AWS::IAM::UserToGroupAddition'
    Properties:
      GroupName: !Ref IAMGroupAllMember
      Users:
        - !Ref IAMUserProd1
        - !Ref IAMUserProd2

権限の補足

IAMグループの使いどころ

権限は次の方針で設定しています。

  • ユーザ自体には何の権限も与えない
  • IAMロールで権限管理

しかし、幾つかの権限はロール管理が難しいです。

  • 自分のパスワードの変更

    • 特に、初ログイン時のパスワード変更はユーザ権限での操作以外ムリ
  • 自分のMFA設定
  • 自分のSSH鍵管理
    • OpsWorksを使う場合

「自分の」という部分が曲者です。単に権限を与えるだけでなく、「自分だけできる」というのが重要です。
そのため、これら「自分の情報だけに対して更新権限を与えたい」という場合はIAMグループを使って権限付与しています。

              - Effect: 'Allow'
                Action:
                  - 'iam:GetLoginProfile'
                  - 'iam:ChangePassword'
                  - 'iam:CreateVirtualMFADevice'
                  - 'iam:DeleteVirtualMFADevice'
                  - 'iam:EnableMFADevice'
                Resource: 
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:mfa/${!aws:username}' # 自分のMFAだけを対象に
                  - !Sub 'arn:aws:iam::${AWS::AccountId}:user/${!aws:username} # 自分のユーザ情報だけを対象に

OpsWorksはActionレベルで自分だけに対してのものを用意してくれているのでそれを使います。

              - Effect: 'Allow'
                Action:
                  - 'opsworks:UpdateMyUserProfile' # 自分の情報だけの更新権限, 参照権限版もある(DescribeMyUserProfile)
                  - 'opsworks:Describe*'
                Resource: '*'

AssumeRoleの条件

AssumeRoleは、ユーザの指定以外にも特定の条件を満たした場合のみ、という制約をつけることができます。

本番運用者と請求管理者にはユーザの指定以外にMFAでログインしている場合のみAssumeRoleを許可するという制約をつけました。

      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS:
                - !GetAtt IAMUserBilling1.Arn
            Action: 'sts:AssumeRole'
            Condition:
              Bool:
                aws:MultiFactorAuthPresent: true # MFAログインでないとConditionが満たせずAssumeRoleできない

さいごに

AssumeRoleを理解してからは権限管理の世界が広がった感があります。
ぜひ権限を追加して、実態に即したロールに育ててください。

さいごのさいごに

(IAMの具体的な使い方を紹介する記事がもっと増えますように)

続きを読む

CloudFormationとecs-cliを使って「Fargate+NLB+fluentd」環境を作成する。

AWS Fargateが発表されたのでさっそくつかってみます。
今回はFargateの上でfluentdコンテナを実行します。

手順

  1. CloudFromationのテンプレートでECSクラスタやNLBなどのAWSリソースを作ります。
  2. fluentdのDockerfiledocker-compose.ymlecs-params.ymlを作ります。
  3. fluentdのDockerイメージを作成し、ECRにpushします。
  4. ecs-cliコマンドでFargateにデプロイします。
  5. ecs-cli logsコマンドで接続を受け付けていることを確認します。

CloudFormationテンプレート

  • VPCから一気に作ります。
AWSTemplateFormatVersion: '2010-09-09'

Description: ""

Parameters:
  VPCCidr:
    Description: ""
    Type: String
    Default: "10.1.0.0/16"
  EnableDnsSupport:
    Description: ""
    Type: String
    Default: true
    AllowedValues:
      - true
      - false
  EnableDnsHostnames:
    Description: ""
    Type: String
    Default: true
    AllowedValues:
      - true
      - false
  InstanceTenancy:
    Description: ""
    Type: String
    Default: default
    AllowedValues:
      - default
      - dedicated
  SubnetCidrA:
    Description: ""
    Type: String
    Default: "10.1.10.0/24"
  SubnetCidrB:
    Description: ""
    Type: String
    Default: "10.1.20.0/24"
  AvailabilityZoneA:
    Description: ""
    Type: String
    Default: us-east-1a
  AvailabilityZoneB:
    Description: ""
    Type: String
    Default: us-east-1b
  MapPublicIpOnLaunch:
    Description: ""
    Type: String
    Default: true
    AllowedValues:
      - true
      - false
  AttachInternetGateway:
    Description: ""
    Type: String
    Default: "true"
    AllowedValues:
      - true
      - false
  Schema:
    Description: ""
    Type: String
    Default: "internet-facing"
    AllowedValues:
      - "internet-facing"
      - "internal"
  ListenerPort:
    Description: ""
    Type: String
    Default: "24224"
  TargetGroupPort:
    Description: ""
    Type: String
    Default: "24224"
  TargetType:
    Description: ""
    Type: String
    Default: "ip"
    AllowedValues:
      - "ip"
      - "instance"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Network Configuration"
        Parameters:
          - VPCCidr
          - EnableDnsSupport
          - EnableDnsHostnames
          - InstanceTenancy
          - SubnetCidrA
          - SubnetCidrB
          - AvailabilityZoneA
          - AvailabilityZoneB
          - MapPublicIpOnLaunch
          - AttachInternetGateway
      - Label:
          default: "NetworkLoadBalancer Configuration"
        Parameters:
          - Schema
          - ListenerPort
          - TargetGroupPort
          - TargetType

Outputs:
  Cluster:
    Value: !Ref Cluster
  TargetGroupArn:
    Value: !Ref TargetGroup
  SubnetA:
    Value: !Ref SubnetA
  SubnetB:
    Value: !Ref SubnetB
  SecurityGroup:
    Value: !GetAtt SecurityGroup.GroupId
  IAMRole:
    Value: !GetAtt IAMRole.Arn
  Repository:
    Value: !Ref Repository

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidr
      EnableDnsSupport: !Ref EnableDnsSupport
      EnableDnsHostnames: !Ref EnableDnsHostnames
      InstanceTenancy: !Ref InstanceTenancy
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}.vpc"

  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}.rtable"

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}.igw"

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  Route:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  SubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref SubnetCidrA
      AvailabilityZone: !Ref AvailabilityZoneA
      MapPublicIpOnLaunch: !Ref MapPublicIpOnLaunch
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}.subnet"

  SubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetA
      RouteTableId: !Ref RouteTable

  SubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref SubnetCidrB
      AvailabilityZone: !Ref AvailabilityZoneB
      MapPublicIpOnLaunch: !Ref MapPublicIpOnLaunch
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}.subnet"

  SubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref SubnetB
      RouteTableId: !Ref RouteTable

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "SecurityGroup"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - CidrIp: !Ref VPCCidr
          IpProtocol: "tcp"
          FromPort: "24224"
          ToPort: "24224"
        - CidrIp: 0.0.0.0/0
          IpProtocol: "tcp"
          FromPort: "24224"
          ToPort: "24224"
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}.sg"

  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Ref AWS::StackName

  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub "${AWS::StackName}-nlb"
      Type: "network"
      Scheme: !Ref Schema
      Subnets:
        - !Ref SubnetA
        - !Ref SubnetB
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}.nlb"

  Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref LoadBalancer
      Port: !Ref ListenerPort
      Protocol: "TCP"
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn: LoadBalancer
    Properties:
      Name: !Sub "${AWS::StackName}-tg"
      Port: !Ref TargetGroupPort
      Protocol: "TCP"
      VpcId: !Ref VPC
      TargetType: !Ref TargetType
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}.targetgroup"

  IAMRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "${AWS::StackName}.role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "sts:AssumeRole"
            Principal:
              Service:
                - "ecs-tasks.amazonaws.com"
      Policies:
        - PolicyName: !Sub "${AWS::StackName}.policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Resource: "*"
                Action:
                  - "ecr:GetAuthorizationToken"
                  - "ecr:BatchCheckLayerAvailability"
                  - "ecr:GetDownloadUrlForLayer"
                  - "ecr:BatchGetImage"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"

  LogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub "${AWS::StackName}/fluentd"
      RetentionInDays: 3

  Repository:
    Type: "AWS::ECR::Repository"
    Properties:
      RepositoryName: !Sub "${AWS::StackName}/fluentd"
  • ポイント

    1. NLBにはSecurityGroupはアタッチできない。
    2. コンテナにはVPC Cidrからのアクセスを許可する必要がある。これがないとヘルスチェックが失敗する。
    3. コンテナはロググループを自動生成しない。あらかじめロググループを作成しておく必要がある。

Stackの作成

  • AWSコンソールからCloudFormationを選択してStackを作成します。

cfn

  • しばらく待ちます。

creating

  • 完成です。

created

コンテナの設定

  • 以下のディレクトリ構成で設定ファイルを書きます。
├── Makefile
└── fluentd
    ├── Dockerfile
    ├── docker-compose.yml
    ├── ecs-params.yml
    ├── fluent.conf
    └── plugins //今回は空ディレクトリでok

fluent.conf

  • 標準出力に吐き出すだけです。
  • CloudWatchLogsに転送されます。
<source>
  type forward
  bind 0.0.0.0
  port 24224
</source>

<match **>
  type stdout
</match>

Dockerfile

FROM fluent/fluentd
COPY fluent.conf /fluentd/etc

docker-compose.yml

  • ${AWS::Account}はAWSアカウントID、${AWS::StackName}はCloudFormationで作成したスタックの名前を入れます。
version: '2'
services:
  fluentd:
    image: ${AWS::Account}.dkr.ecr.us-east-1.amazonaws.com/${AWS::StackName}/fluentd
    ports:
      - "24224:24224"
    logging:
      driver: "awslogs"
      options:
        awslogs-region: "us-east-1"
        awslogs-group: "${AWS::StackName}/fluentd"
        awslogs-stream-prefix: "container"

ecs-params.yml

  • ${SubnetA}, ${SubnetB}, ${SecurityGroup}はCloudFormationで作成したリソースのIDを入れます。
  • CloudFormationの「出力」タブに作成したリソースの一覧が表示されているのでコピペします。
  • 自動生成してほしい。
version: 1
task_definition:
  ecs_network_mode: awsvpc
  task_execution_role: arn:aws:iam::${AWS::Account}:role/${AWS::StackName}.role
  task_size:
    cpu_limit: 0.25
    mem_limit: 0.5GB
  services:
    fluentd:
      essential: true

run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - ${SubnetA}
        - ${SubnetB}
      security_groups:
        - ${SecurityGroup}
      assign_public_ip: ENABLED
  • この設定ファイルをみて分かる通り、Fargate(正確にはawsvpcモード)ではコンテナに直接サブネットやセキュリティグループをアタッチします。
  • EC2インスタンスのような感覚でコンテナを扱えます。

Makefile

  • たくさんコマンドをうつのでMakefileをつくっておきます。
push:
    docker build -f fluentd/Dockerfile -t ${AWS::Account}.dkr.ecr.us-east-1.amazonaws.com/${AWS::StackName}/fluentd fluentd
    `aws ecr get-login --no-include-email --region us-east-1`
    docker push ${AWS::Account}.dkr.ecr.us-east-1.amazonaws.com/${AWS::StackName}/fluentd:latest

up:
    cd fluentd; 
    ecs-cli compose service up 
        --cluster ${AWS::StackName} 
        --target-group-arn ${TargetGroupArn} 
        --launch-type FARGATE 
        --container-name fluentd 
        --container-port 24224 
        --region us-east-1 
        --timeout 10

rm:
    cd fluentd; 
    ecs-cli compose service rm 
    --cluster ${AWS::StackName} 
    --region us-east-1 
    --timeout 10

Deploy

  • イメージを作ってECRにpushします。
$ make push
docker build -f fluentd/Dockerfile -t ************.dkr.ecr.us-east-1.amazonaws.com/fargate/fluentd fluentd
Sending build context to Docker daemon  5.632kB
Step 1/2 : FROM fluent/fluentd
 ---> 060874232311
Step 2/2 : COPY fluent.conf /fluentd/etc
 ---> Using cache
 ---> 1ee08befeb8d
Successfully built 1ee08befeb8d
Successfully tagged ************.dkr.ecr.us-east-1.amazonaws.com/fargate/fluentd:latest
`aws ecr get-login --no-include-email --region us-east-1`
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded
docker push ************.dkr.ecr.us-east-1.amazonaws.com/fargate/fluentd:latest
The push refers to a repository [************.dkr.ecr.us-east-1.amazonaws.com/fargate/fluentd]
baa346e06fe3: Pushed 
fe129fa31f70: Pushed 
dcf88bef8f3a: Pushed 
b59190601542: Pushed 
56e1e5a28df0: Pushed 
9fc62b353b50: Pushed 
26fbe6ae586e: Pushed 
16174e87921f: Pushed 
latest: digest: sha256:9f8c90b5fc10c084f93c5a93c038f4d307676b4fb641a8a36d67f4573655d52f size: 1981
  • Fargateにデプロイします。
$ make up
cd fluentd; 
    ecs-cli compose service up 
    --cluster fargate 
    --target-group-arn arn:aws:elasticloadbalancing:us-east-1:************:targetgroup/fargate-tg/**** 
    --launch-type FARGATE 
    --container-name fluentd 
    --container-port 24224 
    --region us-east-1 
    --timeout 10
WARN[0000] Skipping unsupported YAML option...           option name=networks
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=fluentd
INFO[0001] Using ECS task definition                     TaskDefinition="fluentd:12"
INFO[0002] Created an ECS service                        service=fluentd taskDefinition="fluentd:12"
INFO[0002] Updated ECS service successfully              desiredCount=1 serviceName=fluentd
INFO[0017] (service fluentd) has started 1 tasks: (task 7228958b-0de1-4e31-a6b2-52d35b6c7b84).  timestamp=2017-12-07 02:14:52 +0000 UTC
INFO[0139] Service status                                desiredCount=1 runningCount=1 serviceName=fluentd
INFO[0139] ECS Service has reached a stable state        desiredCount=1 runningCount=1 serviceName=fluentd

確認

  • ログを取得します。
$ ecs-cli ps --cluster fargate --region us-east-1
Name                                          State                Ports                         TaskDefinition
7228958b-0de1-4e31-a6b2-52d35b6c7b84/fluentd  RUNNING              **.**.**.**:24224->24224/tcp  fluentd:12

$ ecs-cli logs --cluster fargate --region us-east-1 --task-id 7228958b-0de1-4e31-a6b2-52d35b6c7b84
2017-12-07 02:17:03 +0000 [info]: reading config file path="/fluentd/etc/fluent.conf"
2017-12-07 02:17:03 +0000 [info]: starting fluentd-0.12.40
2017-12-07 02:17:03 +0000 [info]: gem 'fluentd' version '0.12.40'
2017-12-07 02:17:03 +0000 [info]: adding match pattern="**" type="stdout"
2017-12-07 02:17:03 +0000 [info]: adding source type="forward"
2017-12-07 02:17:03 +0000 [info]: using configuration file: <ROOT>
  <source>
    type forward
    bind 0.0.0.0
    port 24224
  </source>

  <match **>
    type stdout
  </match>
</ROOT>
2017-12-07 02:17:03 +0000 [info]: listening fluent socket on 0.0.0.0:24224
  • 正常に起動しています。
  • fluent-cattelnetで接続するとログが出力されます。

削除

  • 作成したリソースを消しておかないとお金を取られます。
  • コンテナを削除してから、AWSリソースを削除します。
$ make rm
cd fluentd; 
    ecs-cli compose service rm 
    --cluster fargate 
    --region us-east-1 
    --timeout 10
WARN[0000] Skipping unsupported YAML option...           option name=networks
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=fluentd
INFO[0001] Updated ECS service successfully              desiredCount=0 serviceName=fluentd
INFO[0001] Service status                                desiredCount=0 runningCount=1 serviceName=fluentd
INFO[0017] (service fluentd) has begun draining connections on 1 tasks.  timestamp=2017-12-07 02:44:53 +0000 UTC
INFO[0017] (service fluentd) deregistered 1 targets in (target-group arn:aws:elasticloadbalancing:us-east-1:************:targetgroup/fargate-tg/****)  timestamp=2017-12-07 02:44:53 +0000 UTC
INFO[0321] Service status                                desiredCount=0 runningCount=0 serviceName=fluentd
INFO[0321] ECS Service has reached a stable state        desiredCount=0 runningCount=0 serviceName=fluentd
INFO[0321] Deleted ECS service                           service=fluentd
INFO[0322] ECS Service has reached a stable state        desiredCount=0 runningCount=0 serviceName=fluentd
  • AWSコンソールでECRリポジトリを削除します。

    • CloudFromationで作成したリポジトリにイメージが登録されていると、CloudFormationでは削除できません。
  • AWSコンソール -> CloudFromation -> Stackの削除をします。

使ってみた感想

今まではECSクラスタを構成するEC2インスタンスの制限を受けて使いにくい部分(動的ポートマッピングとかawsvpcのeni制限とかスケールとか)がありましたが、Fargateによってそれらが一気に解決しました。AWSでコンテナ運用するならFargate一択ですね。

参考

  1. AWS Fargate: サービス概要
  2. GitHub – aws/amazon-ecs-cli

続きを読む

AWS EC2上のUbuntu16.04にデスクトップ環境を構築してMacからつなぐ

はじめに

やりたいこと

AWS EC2上にUbuntu16.04のインスタンスをたてて、そのインスタンスにMacからリモートデスクトップが接続する

なんでこの記事を書いたか

ぐぐってみると、日本語、英語で色々なドキュメントが出てきた。それらを参考に色々試したがサクッとはいかなかった。「どれを参考にすればいいねん!」って人向けに「これを実行すればサクッと構築できるよ」という情報を提供したかったから書いた。
ちなみに、X Window System、ubuntu-desktop、xrdp、Xfce4、GNOME、KDE、Unity、vncなどなど、
用語がわからなすぎたのでハードルが高く感じた。

いまはただのメモ

とりあえずメモ代わりに投稿するかってくらいのモチベーションで投稿してます。
もしここの記述がようわからんとかあれば、コメントいただければ少しずつ細かく書いていきます。

想定している読者レベル

  • EC2インスタンスをたてたことがある
  • なんとなくlinuxコマンドがつかえる

用語

X Window System

ディスプレイにウィンドウを表示したり、マウスやキーボードを使ってやりとりするためのシステム。WindowsやMacではOSとこのソフトウェアが一体化しているが、Linuxでは一体化してないのでもし先述のことを行いたい(=GUI)場合はインストールする必要がある。MacのOS Xの「X」とWindowsの「Window」がまざった「X Window System」という名称で紛らわしい。単に「X」と呼ばれることもある。

Xfce, GNOME, KDE, Unity,

デスクトップ環境を実現するためのシステム。色々あってややこしいが、デスクトップ上のデザインや機能が微妙に違うだけ。今回はXfceを使った。

vnc

ネットワーク越しに、別のコンピューターのデスクトップ画面を表示してGUI操作することができるシステム。今回Ubuntuで環境を構築するにあたって参照した記事ではvnc4serverかTightVNCのどちらかを使っていることが多かった。

手順

EC2インスタンス作成

下記参照にEC2インスタンスを作成
– AMIはクイックスタートにあるUbuntu 16.04
– セキュリティグループのインバウンドにポート5901を追加
– とりあえずt2.microでいい

デスクトップ環境構築

この記事のとおりでOK

sudo apt update && sudo apt upgrade

sudo sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
sudo /etc/init.d/ssh restart

sudo passwd ubuntu

sudo apt install xrdp xfce4 xfce4-goodies tightvncserver

echo xfce4-session> /home/ubuntu/.xsession

sudo cp /home/ubuntu/.xsession /etc/skel

sudo sed -i '0,/-1/s//ask-1/' /etc/xrdp/xrdp.ini

sudo service xrdp restart

tightvncserver

つなぐ

Finder起動->移動->サーバへ接続で、vnc://XX.XX.XX.XX:5901に接続。
XX.XX.XX.XXは作成したEC2インスタンスのIPかパブリックDNSを指定。
ただしvncはそのままでは暗号化されてないので、セキュアなことをやりたい場合はご自身で対応をお願いします。

ついでに日本語化

chromeをインストールしたら、日本語が文字化けしてたので日本語化してみた。
“Language support” icon missing in System Settings
英語環境でセットアップしてしまったUbuntuで日本語を使えるようにする

補足

  • tightvncserverでなくてvnc4serverでもOKだと思います(試してない)
  • xrdpはaptからのインストールでOK

続きを読む

CloudGarage DAPとAWS Cloud9で無料開発環境

CloudGarageでAdvent Calendarを書くにあたって何を書こうか考えてたら、re:Inventでちょうど良さそうなサービスがローンチされたので試してみた。

AWS Cloud9

ちょうど良さそうサービスとはAWS Cloud9
Cloud9というWeb IDEサービスをAWSが買収して1年程度経つがとうとうAWSに統合された。
まだ今までの環境も使える。使えるが普通に行くとAWS Cloud9に飛ばされるので直接ログインページにアクセスする必要がある。
ちなみに最近開発環境としてCloud9をよく使っていたので、ログイン方法がわからなくなったのは秘密だ。

料金体系を調べるとどうもAWS Cloud9の利用料金は無料らしい。
動作環境にEC2を使うとその分課金される、が自前のサーバを動作環境にすることができる。
その場合は無料らしい。

CloudGarageのリリースパーティでLTをした際にDev Assist Program(DAP)で貰ったインスタンスもあるし、AWS Cloud9と合わせて無料の開発環境を構築することにする。(よし、タイトル回収完了)

環境構築

前準備

さて、まずは前準備。
AWS Cloud9で使用するユーザを作成する。
色々インストールとかするのでsudo権限が必要。
なお、AWS Cloud9が色々インストールする際にパスワード入力が一々求められて面倒なので、パスワードなしでsudoできるようにしておくと便利。
なので/etc/sudoers.d/に以下のようなファイルをおいて置くといいだろう。名前はなんでもいい。

# ここではユーザ名をcloud9としている
cloud9 ALL=NOPASSWD: ALL

AWS Cloud9ではPython 2.7とNode.js 0.6.16以降が必要。
(Pythonは2.7以降ではなく2.7が必要)

Pythonはバージョン固定とか面倒そう(実際は知らない。標準で入っていたりするし勝手にアップデートされても面倒とかそんな感じ)なので、anyenvを使ってPythonとNode.jsをインストールした。

anyenvの使い方はこの記事を見ればいいと思う(面倒なので書かない)。

AWS Cloud9のページへ

AWS Cloud9は実はまだ提供リージョンに制限がある。

  • 米国東部(バージニア北部)
  • 米国東部(オハイオ)
  • 米国西部(オレゴン)
  • EU(アイルランド)
  • アジアパシフィック(シンガポール)

の5リージョンでしか提供されていないので好きな場所を選んで欲しい。
最初にアクセスすると以下のようなページが表示されるので”Create Environment”をクリックする
001.PNG

環境名と備考の設定

環境名を指定する。
備考は何かあれば書けばいい。
とりあえず何を書いていいかわからいので放置。
002.PNG

環境設定

とりあえずEC2を使うことになっているので、これを”Connect and run in remote server (SSH)”に変更する。
003.PNG

すると、SSH接続の設定フォームが表示されるので自分の環境に合わせて入力する。
AWSが接続するための公開鍵が生成されているので、~/.ssh/authorized_keysに追記する。
004.PNG

設定が終わったら、右下の”Next Step”をクリック。
接続テストが行われて問題がなければ、この設定でいいか確認を受けるので右下の”Create environment”をクリック。
するとWeb IDEの画面に遷移しようとする。

Cloud9のインストール

遷移しようと別のページに飛ぶがまだ使用はできない。
モーダルウィンドウに”AWS Cloud9 installer”が表示される。
基本的に”Next”を押し続ければ問題はない。
最低限のものでよければ”Cloud9 IDE”と”c9.ide.find”(チェックを外せないもの)をインストールするといいだろう。
005.PNG

完了するとこんな画面になるので、あとはサンプルとか見ながらいろいろやってみるといいと思う。
006.PNG

まとめ

これでDAPとAWS Cloud9による無料開発環境の構築ができたと思う。
いやCloudGarageというよりもAWS Cloud9の記事じゃねーかと感じる人もいると思うが異論は受け付けない。
CloudGarageで動かしているからいいのだ。そうじゃなければ一体何を書けというのだ。マジでネタがなかったんだ。

ということで本記事はここまでだ。
明日は@FoxBoxsnetさんです。よろしくお願いいたします。

続きを読む

AWS(EC2) で Python3 + Flask を Apache で動かす(mod_wsgi?)

はじめに

簡単なWebサービス新しく立ち上げるにあたり、勉強を兼ねて Python を使うことにしたので、その環境構築用のメモとして残す。ほとんど、他の記事を寄せ集めただけなのだけど、mod_wsgiのインストールで少し手間取ったので念のため。

参考:PythonのWebフレームワーク4種比較
https://qiita.com/Morio/items/8db3c9b3ba7f2c71ef27

AWS (EC2) + Apache2

下記のサイトを参考にしてテスト用のEC2サーバのインスタンスを作成後、Apache2 をインストール・起動します。なお、現時点(2017/12/06)でのバージョンは、2.2.34だった。

参考:AWS EC2でWebサーバーを構築してみる
https://qiita.com/Arashi/items/629aaed33401b8f2265c

Python3 + pip3

下記のサイトを参考にして Python3 をインストールし、pip を設定。現時点(2017/12/06)では、python3.6がリリースされているので、3.5ではなく3.6をインストール。

参考:Amazon Linux (EC2)上でPython3と…
https://qiita.com/KeijiYONEDA/items/f9cf37cfc359aa893797
※「3.5」は「3.6」に、「35」は「36」に読み替え

実際に実行したコマンド.
sudo yum install python36-devel python36-libs python36-setuptools
sudo /usr/bin/easy_install-3.6 pip

Flask + mod-wsgi

Flask のインストールは簡単。下記のサイトの最初の5分だけを実行すれば、Hello Flask! を表示するWebサイトは完成!

参考:PythonのFlaskを初めて触ってから30分で…
https://qiita.com/yoshizaki_kkgk/items/3cd785b4a670deec0685
※ 環境構築からHello Flask!まで(5分)だけを実行

・・・でも今回は Apacheサーバを利用するので、mod_swgi のインストールが必要となる。だけど、上記サイトの次の10分以降に書いてあるようにソースから configure・make なんて 前時代的 というか 面倒くさい というか、難しいことは避けたいので、pipに頼ることにする

pip3 install mod-wsgi

このコマンドでインストールできそうなのだけど、下記のエラーが出て前に進まなくなった。apxs って何さ?

Collecting mod_wsgi
  Using cached mod_wsgi-4.5.22.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-build-4lb9c3ll/mod-wsgi/setup.py", line 301, in <module>
        INCLUDEDIR = get_apxs_config('INCLUDEDIR')
      File "/tmp/pip-build-4lb9c3ll/mod-wsgi/setup.py", line 273, in get_apxs_config
        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      File "/usr/lib64/python3.6/subprocess.py", line 707, in __init__
        restore_signals, start_new_session)
      File "/usr/lib64/python3.6/subprocess.py", line 1333, in _execute_child
        raise child_exception_type(errno_num, err_msg)
    FileNotFoundError: [Errno 2] No such file or directory: 'apxs'

apxs = Apache extension tool

ググってみたら、apxs は「APache eXtenSion tool」のことだそうで、apache-dev に含まれるそうな。早速、以下のコマンドで apache-dev をインストール。しかし、省略難しすぎ。

sudo yum install httpd-devel

これで大丈夫!と思ったけど、次は、gcc が入っていなくて怒られました。仕方ないので、gcc を yum 経由でインストール。

sudo yum install gcc
sudo /usr/local/bin/pip3 install mod_wsgi

デプロイ

必要なものは揃った!ってことで、Apache の設定とサンプル用のサイトを作ってみる。

サイトの作成

Hello Flask!をそのまま流用させていただく。

/var/www/flask/app.py
# coding: utf-8
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello, Flask!"

if __name__ == "__main__":
    app.run()

swgi ファイル

Apache というか、mod_swgiに読み込ませるファイルを作成

/var/www/flask/adapter.wsgi
# coding: utf-8
import sys
sys.path.insert(0, '/var/www/flask')

from app import app as application

Apacheの設定

Apache の conf.d に設定ファイルを追加し、app.py と adapter.wsgi が有効になるように設定。

/etc/httpd/conf.d/flask.conf
# /etc/httpd/modules には配置されないので指定が必要
LoadModule wsgi_module /usr/local/lib64/python3.6/site-packages/mod_wsgi/server/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so

<VirtualHost *:80>
  ServerName ec2-xxxxxxx.amazonaws.com:80
  DocumentRoot /var/www/flask
  WSGIScriptAlias / /var/www/flask/adapter.wsgi
  <Directory "/var/www/flask/">
    options Indexes FollowSymLinks +ExecCGI
  </Directory>
</VirtualHost>

まとめ

python3、pip 辺りは他にも利用するだろうから、すでにインストールされている場合が多そう。なので、実質面倒なのは Apache(mod_wsgi)の設定周りかな。パッケージ化が進んでいると思っていたし、yum / pip といった便利なものがあるにも関わらず、gccが必要になるとは思わなかった。

次は、flask + DataBase + Template Engine ってとこかな。

続きを読む

(゚∀゚)o彡 sasata299’s blog

この記事は Classi Advent Calendar 2017 の6日目の記事です。連投です。 redashというのは、OSSで提供されているデータの可視化ツールです。 ・いろいろなデータソース(MySQL, Spreadsheet, etc..)からデータを取得できる・取得したデータの可視化ができる といった特徴があります。 データの可視化ってみんな好きで … 続きを読む