2017年1月18日水曜日

【AWS】Amazon LinuxでGitlabを強制SSL通信(https)で接続できるように構築してみた

Amazon LinuxでGitlabを強制SSL通信(https)で接続できるように構築してみた

・はじめに

AWSを使ってGitlabに強制SSLで繋げるように構築しようという内容です。
ついでにロードバランサも使います。記事中では1インスタンスのみなので恩恵は少ないですが・・・。
細かいところは基本的に他記事に頼ります。

 
・バージョン

GitLab Community Edition 8.12.1
Amazon Linux 2016.09


・お金の話

放置で月3000円ほどコストがかかります。
放置で月5000円ほどコストがかかります。
ドメインの取得は年1000円ほどかかります。


・AmazonLinuxの準備

EC2で作成します。
インスタンスの種類はt2.smallがお勧めです。
t2.microの場合はメモリ不足で動きませんでした。
どのリージョンでもできますが、私は東京リージョンで構築しました。

作成は以下の記事を参考にしてください。
http://qiita.com/hiroshik1985/items/f078a6a017d092a541cf


もしも後から人が増えてメモリが足りなくなっても以下の記事のようにスペックは上げれるので安心です。

・VPCの作成

以下を参考にVPCを作成します。今回、ロードバランサを使用するためサブネットは2つ作る必要があります。
http://qiita.com/hiroshik1985/items/9de2dd02c9c2f6911f3b


・インスタンス(EC2)のセキュリティグループの作成

セキュリティグループを作成し、デフォルトのままでインスタンス(EC2)に設定します。
作成は以下の記事を参考にしてください。
http://qiita.com/hiroshik1985/items/f078a6a017d092a541cf


・Elastic IPの割り当て

EC2のIPアドレスが再起動をすると変わってしまうため、静的IPアドレスを割り当てることで固定します。
以下の記事を参考にしてください。
https://ac-5.net/aws/aws_elasticip_allocation


・ドメインと証明書の準備

Route53で取得します。
以下の記事を参考にしてください。現在は東京リージョンでもCertificate Managerを利用できるので、「発行した証明書の利用」まで実施できます。
http://qiita.com/sk565/items/2da1fc0c5fc676f54994


・ロードバランサの作成とDNSの設定

以下を参考にAWSで証明書のインストールのためのロードバランサを作成し、DNSの設定をします。
http://kawatama.net/web/1157


・ロードバランサの設定

ロードバランサではHTTPのアクセスを受けた場合、10100ポートでインスタンス(EC2)に送ります。
HTTPSでアクセスを受けた場合はHTTP(80ポート)でインスタンス(EC2)に送ります。
インスタンス側が10100ポートで受け取った場合、HTTPS(443ポート)でリダイレクトすることで、強制的にHTTPS通信で接続させることが可能です。
インスタンス(EC2)側がHTTP(80ポート)を受け取った場合はそのまま通信するようにしています。

以下のような設定でロードバランサ側は実現できます。
AWSの「ロードバランサ」→「説明」内のポート構成から変更します。



・ロードバランサのセキュリティグループの変更

ロードバランサにインスタンス(EC2)のセキュリティグループを設定していますが、ロードバランサ用ととインスタンス(EC2)用の2つに分けます。

セキュリティの関係で必要のない通信は遮断します。
今回はHTTPとHTTPSのみ許可するような設定にしました。

以下のようにセキュリティグループをロードバランサに設定してください。
アウトバウンドの設定はデフォルトで、インバウンドの設定は下記画像を参考にしてください。



・インスタンス(EC2)に設定したセキュリティグループの編集

ロードバランサと同様に不要なアクセスは制限します。
特にインスタンス(EC2)はロードバランサからのみアクセスを受け付けるようにします。

HTTPの送信元はロードバランサのセキュリティグループを指定します。
カスタムTCPルールにはポート範囲に10100を指定し、送信元にはロードバランサのセキュリティグループを指定します。
すべてのICMPも送信元にロードバランサのセキュリティグループを指定します。
→ロードバランサのヘルスチェックはpingを使用しているため、ICMPを受け付ける必要があります。
SSHは開かなくても良いですが、インスタンス(EC2)を弄るたびに変更するのもめんどくさいので、開いておきます。その際、送信元に自分がインスタンス(EC2)に接続している端末(PC等)のグローバルIPアドレスを設定することで、不要なアクセスを防ぎます。
アウトバウンドはデフォルトのままでよいです。

以下の画像を参考に設定します。



ここからインスタンス(EC2)を弄っていきます。

・インスタンス(EC2)にsshでアクセス

以下の記事を参考にsshでアクセスします。
http://dev.classmethod.jp/cloud/aws/aws-beginner-ec2-ssh/


・opensslとgitlabをインストール

以下のコマンドでopensslのインストールをします。
  sudo yum –install openssl

以下のコマンドでgitlabのインストールをします。
  curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
  sudo yum install gitlab-ce


・gitlabの設定

以下のコマンドでファイルを開きます。
 
  sudo vi /etc/gitlab/gitlab.rb

以下の項目を変更します。
 external_url 'http://ドメイン名'
 nginx['custom_nginx_config'] "include /etc/nginx/conf.d/gitlab.con"


・メール設定(任意)

なくても構いませんが、gitlabからの通知用メールアドレスを設定したい場合はgmailアドレスを指定します。
以下の項目を変更し保存します。(ファイルはgitlabの設定と同じ /etc/gitlab/gitlab.rb)
  gitlab_rails['gitlab_email_from'] = 'gmailのアドレス'
  gitlab_rails['gitlab_email_reply_to'] = 'gmailのアドレス'
  gitlab_rails['smtp_enable'] = true
  gitlab_rails['smtp_address'] = 'smtp.gmail.com'
  gitlab_rails['smtp_port'] = 587
  gitlab_rails['smtp_user_name'] = 'gmailのアドレス'
  gitlab_rails['smtp_password'] = 'gmailのパスワード'
  gitlab_rails['smtp_domain'] = 'smtp.gmail.com'
  gitlab_rails['smtp_authentication'] = 'login'
  gitlab_rails['smtp_enable_starttls_auto'] = true
  gitlab_rails['smtp_tls'] = false
  gitlab_rails['smtp_openssl_verify_mode'] = 'peer'



・nginxの設定

先ほど行ったロードバランサの設定で、httpアクセスの場合はポート10100で送るようになっています。
そのため、インスタンス(EC2)側でポート10100を受け取った場合、httpsでロードバランサにリダイレクトしてあげることで、httpでアクセスが来てもhttpsで通信を行うようにします。

以下コマンドでnginxの設定ファイルを開きます(ファイルがない場合は作成)
  sudo vi /etc/nginx/conf.d/gitlab.conf

ファイル内に以下の記述し、保存します。
  server {
    listen 10100;
    server_name ドメイン名;
    rewrite ^(.*)$ ドメイン名 permanent;
  }


・gitlabの立ち上げ

下記コマンドで設定を読み込み直します。
  gitlab-ctl reconfigure

下記コマンドでgitlabの実行です。
  gitlab-ctl start

既に立ち上がっていた場合は下記コマンドで再起動します。
  gitlab-ctl restart


・ブラウザでアクセス

下記URLで設定したドメインにアクセスをします。
  https://ドメイン名/


・ページが表示されない場合

/var/log/nginx/error.log でエラー内容を確認できます。


・アバター画像がHTTPで取得してしまう問題対応

せっかくhttpsにしたにもかかわらず、アバター画像のみhttpで取得してしまう問題がありました。

以下の記事を参考にしたところ解決しました。
https://xnn.sakura.ne.jp/blog/2014/08/gitlab_avatar_problem_under_reverse_proxy/


以下コマンドでファイルを開きます。
  sudo vi /opt/gitlab/embedded/service/gitlab-rails/app/models/user.rb

663行目付近を以下のようにします。


ソースを変えたら以下二つのコマンドで再起動します。
  gitlab-ctl reconfigure
  gitlab-ctl restart



gitlab自体の使い方は以下を参考にしてください。
http://dotnsf.blog.jp/tag/gitlab


長くなりましたが、上記のような流れで強制SSL通信(https)でのgitlab運用が可能です。


2016年4月23日土曜日

Slack連携のHubotをAWS(EC2)でデーモン化してログアウト後も動作させる

概要
最近はSlackとHubotでお遊び中です。
HubotはHerokuで動かすのが良いらしいけどめんどくさいので、元々使っていたAWS(EC2)でHubotを動かしてみます。
SSH等で接続してHubotを動かしてもログアウト後には停止してしまいます。
そこで、ログアウト後にも動作してくれるようにnpmのforeverを利用します。
Hubotをインストールして動かすのは他ブログにたくさんあるので、今回は実際にサーバで動かし続ける方法を紹介します。

foreverのインストール
$ sudo npm install forever -g

foreverでHubotをデーモン化
$ forever start -c coffee node_modules/.bin/hubot --adapter slack

デーモン化したHubotの再起動
スクリプトを書き換えた場合には再起動が必要です。 foreverでデーモン化したHubotを再起動する場合にはscript名を下記コマンドで調べます。
$ forever list



私の場合は「z_tV」でした。


下記コマンドで再起動ができます。
$ forever restart z_tV

2015年7月16日木曜日

メモ

同じ意味

class Human(object):
    def __init__(self, head=None, arms=None, feet=None):
        self.head = head or {}
        self.arms = arms or {}
        self.feet = feet or {}


class Human(object):
    def __init__(self, head=None, arms=None, feet=None):
        if head:
            self.head = head
        else:
            self.head = {}
        if arms:
            self.arms = arms
        else:
            self.arms = {}
        if feet:
            self.feet = feet
        else:
            self.feet = {}

2015年4月24日金曜日

PySDL2でマウスのどのボタンを離したかを検知

概要
今回は、PySDL2の画面上でマウスのどのボタンを離したかを検知する方法です。
ソースコード(github)
ソースコード
__author__ = 'Tanaka'

import sdl2
import sdl2.ext
import sys


class SoftwareRenderer(sdl2.ext.SoftwareSpriteRenderSystem):
    def __init__(self, window):
        super(SoftwareRenderer, self).__init__(window)

    def render(self, components):
        sdl2.ext.fill(self.surface, sdl2.ext.Color(0, 0, 0))
        super(SoftwareRenderer, self).render(components)


def run():
    sdl2.ext.init()
    window = sdl2.ext.Window("mouse-move-event", size=(640, 480))
    window.show()

    world = sdl2.ext.World()
    sprite_renderer = SoftwareRenderer(window)

    world.add_system(sprite_renderer)

    old_mouse_state = None
    
    running = True

    while running:
        # イベントを取得
        events = sdl2.ext.get_events()

        # 一つずつイベントを処理
        for event in events:
            # QUITイベント
            if event.type == sdl2.SDL_QUIT:
                running = False
                break

            # ボタンの状態を取得
            mouse_state = sdl2.mouse.SDL_GetMouseState(None, None)

            # マウスを離したイベントを検知
            if event.type == sdl2.SDL_MOUSEBUTTONUP:
                print("SDL_MOUSEBUTTONUP")

                # 左ボタンが押されている場合 mouse_state == 1
                if old_mouse_state == sdl2.mouse.SDL_BUTTON_LEFT:
                    print("左ボタンが離された")

                # ホイールが押されている場合 mouse_state == 2
                elif old_mouse_state == sdl2.mouse.SDL_BUTTON_MIDDLE:
                    print("ホイールが離された")

                # 右ボタンが押されている場合 mouse_state == 3
                elif old_mouse_state == sdl2.mouse.SDL_BUTTON_RIGHT:
                    print("右ボタンが離された")

            old_mouse_state = mouse_state

        sdl2.SDL_Delay(100)
        world.process()

if __name__ == "__main__":
    sys.exit(run())

イベントの取得

まずイベントを取得します。
# イベントを取得
events = sdl2.ext.get_events()


イベントの判別
そのイベントがマウスのボタンを離したことを表しているかを判別するには、
sdl2.SDL_MOUSEBUTTONUP
を使います。
        # 一つずつイベントを処理
        for event in events:
            # QUITイベント
            if event.type == sdl2.SDL_QUIT:
                running = False
                break

            # マウスのボタンを離したイベントを検知
            if event.type == sdl2.SDL_MOUSEBUTTONUP:
                print("SDL_MOUSEBUTTONUP")


マウスを離したときのマウスの状態を取得・判別
mouse_stateに入れているのはマウスの状態を表しています。
これにより、マウスのどのボタンを離したかを検知することができます。
ただし、離したタイミングでマウスのボタンの状態を取得しても離した後の状態が取得されるため、どのボタンを離したか検知できません。そのため、離される前に押されていたボタンの状態から、離されたボタンを検知します。
            # マウスボタンの状態を取得
            mouse_state = sdl2.mouse.SDL_GetMouseState(None, None)

            # マウスを離したイベントを検知
            if event.type == sdl2.SDL_MOUSEBUTTONUP:
                print("SDL_MOUSEBUTTONUP")

                # 左ボタンが押されている場合 mouse_state == 1
                if old_mouse_state == sdl2.mouse.SDL_BUTTON_LEFT:
                    print("左ボタンが離された")

                # ホイールが押されている場合 mouse_state == 2
                elif old_mouse_state == sdl2.mouse.SDL_BUTTON_MIDDLE:
                    print("ホイールが離された")

                # 右ボタンが押されている場合 mouse_state == 3
                elif old_mouse_state == sdl2.mouse.SDL_BUTTON_RIGHT:
                    print("右ボタンが離された")

            old_mouse_state = mouse_state

2015年4月9日木曜日

twitterAPIで取得したツイートをMongoDBに重複しないように登録

概要
ツイートを取得して遊んでいた。
取得したツイートはMongoDBに登録。
その際に、何回かツイートを取得すると重複して登録されていた。
insertしてから重複を消すのもいいけど、今回はinsertする前に重複しないようにする方法を書く。


取得できるツイートデータの構造
今回使っているデータだけ書きます。

id :ツイートのユニークなID
text :ツイートの内容
created_at :投稿時間

この3つのうち、重複チェックに使うのはidです。


前準備
col_new_tweetにコレクション内の一番新しいidを入れておきます。
コレクションの要素が0の場合にはNoneを入れておきます。

col_new_tweet = col.find().sort('tweet_id', -1)
col_new_tweet_id = None if col_new_tweet.count() == 0 else col_new_tweet[0]['tweet_id']


insert
重複している場合にはそこで処理を終わらせます。
1,2,3,4,5
というツイートがあった時に再度実行したときに新しいツイートが2つあるとすると
1,2,3,4,5,6,7
となります。
このとき、数値が低いほど古いツイートです。
取得したツイートは降順になっているため、7,6,5の順番で見ていきます。
そのため、5が重複している時点でそれ以降は処理する必要がない(はず)です。
大まかな流れは下のソースの通りです。
いきなり出てきている変数などもあるため、気になった方はgithubを見てください。

for line in time_line:
    tweets = json.loads(line, "utf-8")
    for tweet in tweets:
        # 重複しているなら処理を終える
        if col_new_tweet_id is not None and tweet['id'] == col_new_tweet_id:
            return

        tweet_id = tweet['id']
        text = tweet['text'].encode('utf-8')
        created_at = ymd_hms(tweet['created_at'])
        col.insert({'tweet_id': tweet_id, 'text': text, 'created_at': created_at})

おわりに
もっと簡単な方法があれば教えてください。

2015年4月8日水曜日

TwitterのStreamingAPIでたまに取得できるdelete付きツイート

概要
Twitter APIでツイートを取得しているとたまにdeleteのついたツイートが流れてくる。
例:
{u'delete': {u'status': {u'user_id_str': u'000000000', u'user_id': 000000000, u'id': 000000000000000000L, u'id_str': u'000000000000000000'}, u'timestamp_ms': u'0000000000000'}}

これが流れてくるとtweet['user']などでツイート情報を取得する際にエラーが出る。


対策
'delete' in tweet:

でdeleteが含まれているツイートならTrueを返してくれる。

2015年4月3日金曜日

pythonのif文と三項演算子

概要
さきほど、他の方のソースコードを見ていたら下記のような書き方が出てきた。
三項演算子は知っていたが、if文と三項演算子を混ぜるやり方は知らなかった。
ちょっと面白そうなので調べてみた。
if a if b else c:
    print('!')

三項演算子
真の場合 if 条件 else 偽の場合

普通のif~elseに直すと、

if 条件:
    真の場合:
else :
    偽の場合

そのままでとても分かりやすい。

今回の場合
if a if b else c:
    print('!')

なんかパッとわからない。

これを普通のif~elseに直すと、
if b:
    if a:
        print('!')
elif c:
    print('!')


となるようだ。
こうするとわかりやすい。
bが真ならaの評価へ。
bが偽ならcの評価となる。

おわりに
ネストしているif文を一行で表そうとすると読みにくく感じる。
慣れの問題なのかな。
いろいろ試してみたソースコード
間違っていたら指摘してください。