Let's encryptを使ったAWS EC2(Amazon Linux)へのSSL導入

かんたんまとめ

Let's encryptは、無料で利用できるSSL証明書です。
今回はAmazon Linuxで構築した社内サーバのSSL証明書の期限が切れそうだったので、
ためしにインストールして見ました。

実際に入れて見てわかったことは、下記の4点です。

  • Amazon Linuxではまだ自動スクリプトでのセットアップ検証がすんでないらしく手動で設定を行う必要がある。
  • 証明書の有効期限は3ヶ月間
  • cronで自動でリニューアルする
  • Aレコードがインストールするサーバを向いている必要がある
    • つまりDNSが切り替わってからでないと導入できない
$ sudo wget https://dl.eff.org/certbot-auto
$ sudo chmod a+x certbot-auto
# 依存パッケージでエラーになったので、一度パッケージを削除のうえ、アップデート→https://goo.gl/mHx4Qe
$ sudo yum remove libstdc++-devel
$ sudo yum update
$ sudo ./certbot-auto certonly --webroot -w /var/www/html --email test@example.com --debug -d www.example.com
license: Agree
share email: Yes
# 下記パスにファイルが自動作成される
$ sudo ls /etc/letsencrypt/live/www.example.com

# apacheの設定ファイルを編集
$ sudo vi /etc/httpd/conf.d/ssl.conf
$ sudo /etc/init.d/httpd configtest # テスト
$ sudo  /etc/init.d/httpd restart

3ヶ月ごとの更新なので、cronで自動化する。
$ sudo vi /etc/cron.d/letsencrypt
00 16 * * 2 root /home/ec2-user/certbot-auto renew --post-hook "service httpd restart"

#更新期限1ヶ月を切ったあと、毎週火曜日の16時に更新が走るかを確認して完了です。

Amazon linuxだと少しハマりましたが、結構簡単に導入できて、cron設定もできました。

手を動かしてみたらDockerをざっくりと理解できた件。

Dockerについては、

  1. コンテナ型の仮想化技術である
  2. アプリケーションの環境をコンテナに閉じ込めてポータブルにできる。
  3. そのため、ホスト OS上の1プロセスで動作するものである
  4. VMWareVirtualBoxのようなホストOSの上でゲストOSを動かすわけではないので、動作が軽い

これぐらいの知識がある程度で始めました。

特に最初の2つのイメージが湧かなかったので、その辺りを身をもって実感するためにいろいろと試してみました。


はじまりはいつもここから
https://docs.docker.com/docker-for-mac/


まずはインストール
https://docs.docker.com/docker-for-mac/install/

$ docker --version
Docker version 17.06.0-ce, build 02c1d87

OK

次にnginxを動かしてみる

$ docker run -d -p 80:80 --name webserver nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
94ed0c431eb5: Pull complete
9406c100a1c3: Pull complete
aa74daafd50c: Pull complete
Digest: sha256:788fa27763db6d69ad3444e8ba72f947df9e7e163bad7c1f5614f8fd27a311c3
Status: Downloaded newer image for nginx:latest
3728cb8621a5504423e833b7b8d14c02842a44b1886f1e150082f58d54205d0

ローカルにイメージがなければ、勝手にDocker Hubから Dockerイメージをダウンロードする模様

コンテナプロセスを確認してみる

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
3728cb8621a5        nginx               "nginx -g 'daemon ..."   4 minutes ago       Up 4 minutes        0.0.0.0:80->80/tcp   webserver

動いているのを確認


コンテナを止めて、再起動してみる

$ docker stop webserver
webserver
$ docker start webserver
webserver

webserverコンテナを削除してみる(nginxイメージは残る)

$ docker rm -f webserver
webserver
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              b8efb18f159b        7 days ago          107MB
hello-world         latest              1815c82652c0        7 weeks ago         1.84kB

一括でコンテナを削除

docker rm $(docker ps -aq)

イメージも削除してみる

$ docker rmi nginx
Untagged: nginx:latest
Untagged: nginx@sha256:788fa27763db6d69ad3444e8ba72f947df9e7e163bad7c1f5614f8fd27a311c3
Deleted: sha256:b8efb18f159bd948486f18bd8940b56fd2298b438229f5bd2bcf4cedcf037448
Deleted: sha256:ba49ddf19ae3c9d08d348b3f621faca9c2bf4030a28f249af512d76fc9010cb9
Deleted: sha256:7b8885a6166d207114b95dacfe115f9cf8fd023e69bd0d4d1ca30736cf03ca15
Deleted: sha256:eb78099fbf7fdc70c65f286f4edc6659fcda510b3d1cfe1caa6452cc671427bf

macでは~/Library/Containers/com.docker.docker/Data あたりにイメージファイルなどが保存される模様

テスト用のhello-world イメージを削除しようとすると、怒られる。

$ docker rmi hello-world
Error response from daemon: conflict: unable to remove repository reference "hello-world" (must force) - container 3e350518xxxx is using its referenced image 1815c826xxxx

実際には、

$ docker stop eager_shannon
$ docker rmi {image_id}

としないといけないといけない


ubuntu コンテナをベースにapache + php7環境のコンテナを作ってみる

公式のubuntuコンテナをダウンロード

$ docker pull ubuntu
; lampという名前のコンテナをubuntuイメージからdetachモードで作成してbashを起動
$ docker run -itd --name lamp ubuntu bash

; シェルに入る
$ docker attach lamp

; Dockerのubuntu コンテナにphp mysql apache2を入れてイメージを作成する
# apt-get update
# apt-get -y install apache2
# a2enmod ssl
# a2ensite default-ssl
# apachectrl restart
# apt-get install apt-file
# apt-file update
# apt-get install software-properties-common
# add-apt-repository ppa:ondrej/php
# apt-get update
# apt-get install php7.1
# apt-get install php7.1-mysql
# apt-get install php7.1-imagick
# apt-get install php7.1-mbstring
# apt-get install php7.1-bz2
# apt-get install php7.1-xml
# apt-get install php7.1-sqlite
# apt-get install php7.1-intl
# apt-get install php7.1-xdebug
# apt-get install php7.1-memcache
# apt-get install php7.1-redis
# apt-get install php7.1-mongo
# apt-get install php7.1-gd
# apt-get install vim
# apt-get install git
# vi /var/www/html/index.php
# apt-get install sysv-rc-conf
# exit 
; my-apache-php7という名前でコンテナをイメージとして作成
$ docker commit {container_id} my-apache-php7
; 一度containerを削除
$ docker rm {container_id}
MYSQLのコンテナを作成する
$ docker pull mysql/mysql-server
$ docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=secret -d mysql/mysql-server:5.7

#mysqlコンテナのIPアドレスを調べる

$ docker inspect mysql-container | grep IPAddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.3",
                    "IPAddress": "172.17.0.3",

#他のコンテナから利用できるようにgrantを発行

$ docker exec -it mysql-container mysql -uroot -p
mysql> grant all on *.* to root@"172.17.%" identified by "secret";
mysql> flush privileges;

#アプリケーション側からは下記のような形で接続できる

$link = mysqli_connect("172.17.0.3", "root", "secret", "mysql”); //php

#作成したイメージからコンテナを作成してapacheを起動

$ docker run -itd -p 8080:80 --name webserver my-apache-php7 apachectl -D FOREGROUND
#ソースファイルをデプロイ
$ docker exec -it webserver git clone https://github.com/chienowa/docker-test.git /var/www/html/app
# pullする場合
$ docker exec -it webserver bash -c "cd /var/www/html/app && git pull"
# ファイルコピーする場合(mysql接続用ファイルをコンテナにホストからコピー)
$ docker cp mysql.php 1349871fa1ff:/var/www/html/

これでなんとなく、Dockerイメージの作成、コンテナの使い方、ソースのデプロイ方法のイメージが掴めたかな!?



dockerのインストールからコンテナ利用を試行錯誤してみてわかったことをまとめてみました。

  • Dockerコンテナは1つのフォアグラウンドで動くサービスと、そのサービスが依存するライブラリ/モジュールとで構成されるものという理解が、一番ベストプラクティスに近い気がする
  • dockerのコンテナとはアプリケーション環境の複数のプロセスをまとめるものではなく、apache+phpとそのモジュールたち、rubyとgemとrails、など、1プロセスに必要な依存ソフトウェアをコンテナにまとめるのが正しい使い方という理解
  • 1コンテナ内でapachemysqlを二つのサービスを起動させることはできない
  • monitやsupervised を使えば複数のサービスを使えるが、本来的な使い方ではなさそう。
  • たとえばmysqlは別ポートで起動する1サービスなので、別コンテナとして利用するのが本来的な使い方。
  • DockerfileはDockerイメージをビルドするための Infra As a Codeみたいなもの(Vagrantfileに近い)→※今後試す
  • いろんなツールをインストールして、一つのDockerイメージを作り込む(イメージが重くなる)というよりは、軽いベースイメージを元に、Dockerfileで都度イメージを作成するためのもの
  • 本来的にはDockerfileを使ってテキストファイルでセットアップ手順を記述し、コンテナをビルドすると言う形が管理上好ましい
  • 各コンテナは172.17.0.x のようなプライベートIPを振られるので、それを使ってコンテナ間通信ができる。
  • 複数コンテナを管理する際はdocker compose を使ってyamlで複数のコンテナ管理を記述すれば、便利に管理できる!→※今後試す

つぎはAWS ECS上でコンテナを動かしてみようと思います。

WordpressでFS_CHMOD_FILEがらみでエラーが出た時の対処法

とあるセ○ム系サーバでcsvで記事とサムネイルをインポートしようとすると下記のようなエラーが。

PHP Notice:  Use of undefined constant FS_CHMOD_FILE - assumed 'FS_CHMOD_FILE' in ...

とわいえ、FS_CHMOD_FILEはwp-admin/includes/file.php ですでに定義さているし、
再定義しようとしても、すでに定義済みだよと怒られる。

さんざん調べたあげく、
こことか、こことかを参考にしつつ、、、

wp-config.phpに下記を追加することで解決しました。

define('FS_METHOD', 'direct');

通常はdirectがデフォルトのようだけど、変更されていた模様。
面倒だなあ・・

Scrapyでサイトをクロールし、ElasticSearchでサイトの概要を把握する (3)

前回までで、Scrapyでクロールしたページ情報をElasticSearchにインデックスするところまで設定したが、
実はあれだけだと、うまくESやkibana上で検索できなかったりする。
デフォルトではDynamic Mappingといって
データをPOSTすれば勝手にフィールドなどを作ってくれるが、url文字列をうまく扱ってくれなかったり、細かい
データ型の指定や、analyzerの指定ができない。
https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html


なので、scrapyから送る属性をelasticsearchにマッピングしてあげる。


今回は、kibanaのdev toolを使って下記のようなマッピングを行った。

#一度インデックスを削除
DELETE scrapy-2017

#マッピングルールを設定
# - タイトルやdescriptionは日本語解析ができるように
# - URLはngramでなど
PUT scrapy-2017
{
  "settings": {
    "index": {
      "analysis": {
        "tokenizer": {
          "ja_text_tokenizer": {
            "type": "kuromoji_tokenizer",
            "mode": "search"
          },
          "ngram_tokenizer": {
            "type": "ngram",
            "min_gram": 2,
            "max_gram": 2,
            "token_chars": [
              "letter",
              "digit",
              "punctuation",
              "symbol"
            ]
          }
        },
        "analyzer": {

          "ja_text_analyzer": {
            "tokenizer": "ja_text_tokenizer",
            "type": "custom",
            "char_filter": [
              "icu_normalizer"
            ],
            "filter": [
              "kuromoji_part_of_speech"
            ]
          },
          "ngram_analyzer": {
            "tokenizer": "ngram_tokenizer"
          }
        }
      }
    }
  },
  "mappings": {
    "crawledpage": {
      "dynamic": "strict",
      "properties": {
        "title": {
          "type": "text",
          "analyzer": "ja_text_analyzer"
        },
        "description": {
          "type": "text",
          "analyzer": "ja_text_analyzer"
        },
        "url": {
          "type": "text",
          "analyzer": "ngram_analyzer",
          "copy_to": "url_raw"
        },
        "path": {
          "type": "keyword",
          "index": "not_analyzed",
          "ignore_above": 256
        },
        "host": {
          "type": "keyword",
          "index": "not_analyzed",
          "ignore_above": 256
        }, 
        "referer": {
          "type": "text",
          "analyzer": "ngram_analyzer"
        },
        "word_count": {
          "type": "integer"
        },
        "status": {
          "type": "integer"
        }
      }
    }
  }
}

これでkibanaなどで適切に検索ができるようになった。

Scrapyでサイトをクロールし、ElasticSearchでサイトの概要を把握する (2)

Vol1の続き。


今回はScrapyでクロールし、その結果をElasticSearchにインデックスするところまで。

Scrapyのプロジェクトを作成

$ scrapy startproject scrapy_tutorial

ElasticSearchとの連携設定

$ cat scrapy_tutorial/settings.py
ITEM_PIPELINES = {
    'scrapyelasticsearch.scrapyelasticsearch.ElasticSearchPipeline': 500
}

ELASTICSEARCH_SERVERS = ['localhost']
ELASTICSEARCH_INDEX = 'scrapy'
ELASTICSEARCH_INDEX_DATE_FORMAT = '%Y'
ELASTICSEARCH_TYPE = 'items'
ELASTICSEARCH_UNIQ_KEY = 'url'  # Custom uniqe key


実はそんなに接続設定は多くなかったりする。
気にしないといけないのは、インデックスのフォーマットぐらい。
今回は、年ごとにインデックスを作っていく前提でインデックス名を設定した
(例えば、scrapy-2017, scrapy-2018.....)

Scrapyのコーディング

今回は、CrawlSpiderを使ってクローラを作成。
Python初めてだし、日本語の情報も少なく、細かい設定しようとするとはまって結構時間かかりました。

#基本的にはLinkExtractorのコールバックでElasticSearchへ送るITEMを作成
#それ以外は割愛・・
def parse_items(self, response):
....
       return item


#items.py には下記属性を定義
#pathには、urlのディレクトリパスを第二階層まで格納→各階層ごとのページボリュームを計算したかったため
    url = scrapy.Field()
    host = scrapy.Field()
    path = scrapy.Field()
    referer = scrapy.Field()
    title = scrapy.Field()
    description = scrapy.Field()
    word_count = scrapy.Field()
    status = scrapy.Field()

scrapyを実行して、elasticsearchにインデックスされるか確認

$ scrapy crawl myspider
$ curl -XGET 'localhost:9200/scrapy-2017/_search'
#クロールされたページ情報が表示されれば成功

Scrapyでサイトをクロールし、ElasticSearchでサイトの概要を把握する (1)

サイトリニューアルなどで、既存のサイト状況を把握するためのツールとして
サイトをScrapyでクロールし、その統計情報などをElasticSearchにインデックスしてkibanaで解析することを思いついたので、やってみた。

CentOS6へセットアップします

ElasticSearch のインストール

今回は最新の5.x系を利用した。

# rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
# vi /etc/yum.repos.d/elasticsearch.repo
[elasticsearch-5.x]
name=Elasticsearch repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

# yum search java | grep 1.8.0-openjdk
# yum install java-1.8.0-openjdk
# alternatives --config java
# service elasticsearch start


動作確認
こんなのがかえってきたらOK

$ curl -XGET 'localhost:9200/?pretty'
{
  "name" : "UPusCPa",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "YRaDwsW4SIG0SraDDKMPTQ",
  "version" : {
    "number" : "5.2.1",
    "build_hash" : "db0d481",
    "build_date" : "2017-02-09T22:05:32.386Z",
    "build_snapshot" : false,
    "lucene_version" : "6.4.1"
  },
  "tagline" : "You Know, for Search"
}

日本語解析用プラグインインストール

コマンド一発でインストールしてくれて便利。

$ cd /usr/share/elasticsearch
#kuromoji 日本語形態素解析エンジン
$ sudo bin/elasticsearch-plugin install analysis-kuromoji
# 大文字小文字全角半角の正規化ができるプラグイン
$ sudo bin/elasticsearch-plugin install analysis-icu

#プラグインを有効化
$ sudo service elasticsearch restart

#設定ファイルの場所はここ
/etc/elasticsearch/elasticsearch.yml

クロールした情報を可視化するためにKibanaをインストール

# vi /etc/yum.repos.d/kibana.repo
[kibana-5.x]
name=Kibana repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

$ sudo yum install kibana
$ sudo vi /etc/kibana/kibana.yml

server.host: "192.168.33.11"
~
elasticsearch.url: "http://localhost:9200"

$ sudo service kibana start


接続確認
http://192.168.33.11:5601/

python2.7のインストール

$ sudo yum install centos-release-scl-rh
$ sudo yum install python27

#有効化
$ scl enable python27 bash

#恒久的有効化
source /opt/rh/python27/enableを.bashrcに書いておく

Scrapy+ES接続用モジュールをインストール

# pip install --upgrade pip //pip 7から9にアップグレードしないとエラーが出た
# pip install lxml
# pip install cryptography
# pip install ScrapyElasticSearch


これで一通り準備完了