diadia

興味があることをやってみる。自分のメモを残しておきます。

terraform 既存のリソースを使うことについて

今まで勝手に思ってたことにterraformでインフラを立ち上げるときには、terraform内でリソースを生成し、それを組み合わせて環境を作り上げないといけないと思っていた。 しかしながら一度作成したリソースを消さなければ再利用することができる事がわかった。

再利用する場合には、terraformのコード内でリソースIDをハードコーディングすることで呼び出すことができる。

resource "aws_instance" "example" {
  ami                    = data.aws_ami.example.image_id 
  #vpc_security_group_ids = [aws_security_group.example.id]
  vpc_security_group_ids = ["sg-04ea23f14c90378ab"]
  #subnet_id              = aws_subnet.example.id
  subnet_id              = "subnet-0fe3b2c19c920dce4"
  key_name               = "id_rsa_ec2"
  instance_type          = "t2.micro"

  tags = {
    Name = "example"
  }

  user_data = <<EOF
EOF
}

vpc_security_group_idsやsubnet_iの値はterraform内で作成したリソースをid値として取り出すしかないと思っていたが、上記のようにidをマネジメントコンソールから確認しハードコーディングする。このようなやり方でも動くことを確認した。

ECRにpushとpullを試みたときのメモ

ECSを使うためにはどうやらECRが必要だということが判明した。
ECRはdockerhubのようなdocker Imageのコンテナのリポジトリサービスであるようだ。

とりあえずpushとpullをできるようにしたい。

ドキュメントを読んでみると、ECRにpush, pullをするためには認証トークンが必要になると書かれていた。 またAmazon ECRを利用するためには事前準備としてdocker及びaws cliのインストールが必要だとわかった。

結果的にyoutubeの黒川さんの動画が一番わかりやすかったので、それに沿ってメモを残す。
youtube動画

ECRにアクセスするためにIAM ロールをEC2にアタッチする

IAMからロールを作成する。ec2に対して選択し、権限は以下の2つである。

  1. AmazonECS_FullAccess
  2. AmazonEC2ContainerRegistryFullAccess

上述の権限をもつロールをec2にアタッチする。

ECRにpush, pullするためにloginを実行する

# 以下を入力する(アカウントのリージョンに応じてregionの値を変更する)
aws ecr get-login --no-include-email --region us-west-2

# すると文字列が返されるので”docker login -u AWS -p ... ”の最後までをコピペする。

# Login Succeeded と表示される

ECRにpushする

ECRのリポジトリには"プッシュコマンドの表示"を選択すると、imageのビルドとプッシュ方法が書かれている。これに従ってpushすれば良い。

EC2インスタンスに複数のアパッチコンテナを起動して接続を試みる

今まで一つのサーバーにwebサーバーなどのソフトウェアが一つずつ入っている状況が自然なことだと考えてきたので、同じソフトウェアが同時に動く環境を用意し、いわゆるコンテナという概念を体感してみた。

方法としてはec2インスタンスにcontainerを3つ追加する。で一つは単純に既存のbridgeにコンテナを起動する。 残りの2つは、新しく作ったbridgeにコンテナをつなげる。

以下はec2インスタンス起動用のterraform設定ファイルである。

vpc.tf

# VPC

resource "aws_vpc" "example" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true # DNS解決を有効化
  enable_dns_hostnames = true # DNSホスト名を有効化

  tags = {
    Name = "example"
  }
}


# Subnet

resource "aws_subnet" "example" {
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-west-2a"
  vpc_id            = aws_vpc.example.id

  # trueにするとインスタンスにパブリックIPアドレスを自動的に割り当ててくれる
  map_public_ip_on_launch = true

  tags = {
    Name = "example"
  }
}


# Internet Gateway

resource "aws_internet_gateway" "example" {
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example"
  }
}


# Route Table

resource "aws_route_table" "example" {
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example"
  }
}

resource "aws_route" "example" {
  gateway_id             = aws_internet_gateway.example.id
  route_table_id         = aws_route_table.example.id
  destination_cidr_block = "0.0.0.0/0"
}

resource "aws_route_table_association" "example" {
  subnet_id      = aws_subnet.example.id
  route_table_id = aws_route_table.example.id
}


# Security Group

resource "aws_security_group" "example" {
  vpc_id = aws_vpc.example.id
  name   = "example"

  tags = {
    Name = "example"
  }
}

# インバウンドルール(ssh接続用)
resource "aws_security_group_rule" "in_ssh" {
  security_group_id = aws_security_group.example.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
}

# インバウンドルール(http接続用)
resource "aws_security_group_rule" "in_http" {
  security_group_id = aws_security_group.example.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
}


# インバウンドルール(pingコマンド用)
resource "aws_security_group_rule" "in_icmp" {
  security_group_id = aws_security_group.example.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = -1
  to_port           = -1
  protocol          = "icmp"
}

# アウトバウンドルール(全開放)
resource "aws_security_group_rule" "out_all" {
  security_group_id = aws_security_group.example.id
  type              = "egress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
}
ec2.tf


# EC2 Instance

resource "aws_instance" "example" {
  ami                    = "ami-0873b46c45c11058d"
  vpc_security_group_ids = [aws_security_group.example.id]
  subnet_id              = aws_subnet.example.id
  key_name               = "id_rsa_ec2"
  instance_type          = "t2.micro"

  tags = {
    Name = "example"
  }

  user_data = <<EOF
#!/bin/bash
sudo yum update -y
sudo amazon-linux-extras install docker
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -a -G docker ec2-user
EOF
}


# Key Pair

resource "aws_key_pair" "example" {
  key_name   = "id_rsa_ec2"
  public_key = file("../id_rsa_ec2.pub")
}

手順

terraform apply

ec2インスタンスができたらマネジメントコンソールでセキュリティグループを追加する。 TCPで8000 - 9000 を開けておく。

ec2に接続する。

ssh -i id_rsa_ec2 ec2-user@パブリックIP

dockerfileを準備する。

mkdir ~/docker_practice
touch ~/docker_practice/Dockerfile
cd docker_practice
vi Dockerfile
#Dockerfileの内容

FROM centos:latest

RUN yum install -y httpd

#COPY ./index.html /var/www/html/index.html

EXPOSE 80 
RUN echo "Hello Apache." > /var/www/html/index.html

ENTRYPOINT ["/usr/sbin/httpd","-DFOREGROUND"]

次にImageをビルドした後にコンテナを起動していく

docker image build -t docker_practice

docker run -d -p 8000:80 docker_practice

ブラウザにパブリックIP:8000で接続できるか確認してみる。

dockerのbridgeとコンテナの情報を調べる。

docker network ls

docker network inspect bridge 

結果としてbridgeが属するサブネット及びgatewayがわかる。gatewayはbridge のアドレスみたいなものだと現段階では理解している。

次にdockerのコンテナの情報を調べる

docker container ls

docker network inspect (container_id)

この情報からbridgeと同じサブネット内にコンテナが作成されたことがわかった。

次に新しいブリッジを作り、そのブリッジにアタッチしたコンテナを2つ起動させる。

docker network create --attachable -d bridge --subnet=10.99.0.0/16 new_bridge
docker network ls

一覧でnew_bridgeが追加されていればオッケイ.また、docker network inspect new_bridgeでブリッジのサブネットが10.99.0.0/16であるかも確認する。

次にnew_bridgeにコンテナを2つ起動する。

docker run -d -p 8030:80 --net new_bridge docker_practice
docker run -d -p 8090:80 --net new_bridge docker_practice

このコンテナにそれぞれ接続できるかパブリックIP:8030 またはパブリックIP:8090で接続してみる。 できたらそれぞれのコンテナ情報を確認する。

docker container inspect (container_id)

そして内容にnew_bridgeという文言があったり、new_bridgeが属するサブネットが表示されれば、別のブリッジのコンテナが起動していることになる。

これらの確認は黒川さんのyoutubeのやり方をそのまま真似しただけだけど、とても勉強になった。 興味があればこちらを。
https://www.youtube.com/watch?v=h6uw5c5GB_U&list=PLtpYHR4V8Mg-jbuk4yoXhXwJtreodnvzg&index=2

terraformを使えるようにする設定

IAMでterraformからインフラを作成するためのユーザーを作成する。 作成するとアクセスキーとシークレットキーが手に入るので、これを環境変数に加えておくこと。 さらにはデフォルトのリージョンも環境変数として定める。

vi ~/.bash_profile
#追加
#Terraform
export AWS_ACCESS_KEY_ID=AKIAY3ADHOAGPAG5KKEUK
export AWS_SECRET_ACCESS_KEY=J/o+Ijtgtg1UystestCAvfvng7Alb
export AWS_DEFAULT_REGION=ap-northeast-1
#環境変数を反映
source ~/.bash_profile
#以下のコマンドで数字の列が返されたらterraformの使う準備としては第一段階クリア
aws sts getcalleridentity query Account output text
brew install terraform

使い方

terraform init
terraform plan
terraform apply

基本の基本 EC2にssh接続する(復習)

コンテンツ

  1. マネージドコンソールを使ってEC2を立ち上げ、SSH接続を行う
  2. TERRAFORMから同じことを実現させる

一番最初の基本形としてEC2インスタンスを立ち上げて、ssh接続するまでをマネージドコンソールでどのようにセッティングすればよいかメモしておく。

手順

  1. VPC領域を決める
  2. VPC領域内に存在させるサブネットを作成する
  3. インターネットゲートウェイを作成し、VPCにアタッチする
  4. ルートテーブルを作成し、サブネットにアタッチする。ルートテーブルには作成したインターネットゲートウェイをアタッチする。
  5. セキュリティグループを作成し、sshのポートを開けておく。
  6. 最後にec2のインスタンスを作成する。この際に上記で作成したものがきちんと使用されているか注意する。またパブリックIPの確保も気をつける。

相関関係

要素 割り当てる、アタッチ対象
サブネット VPC
インターネットゲートウェイ VPC
ルートテーブル サブネット
セキュリティグループ EC2インスタンス
要素 何を設定スべき
VPC ネットワークの構成
サブネット サブネットの領域
ルートテーブル インターネットに接続したい場合はこちらにインターネットゲートウェイの情報を登録する
セキュリティグループ IPとポートを制限しているが、sshやhttp,DBの接続用にポートを開けること考えなければならない。

参考資料

【AWS EC2 エラー】ssh port 22 Operation timed out - Qiita

AWS EC2でパブリックDNS/IPが割り当てられない - huamutouの日記

pingで通信ができるようにする(補足)

さくらVPScentosでは特にicmpプロトコル(PINGが使うプロトコル)に関わるファイアウォールを設定しなくてもpingを通すことができた。 しかしec2の場合はicmpのポートを開けなければ、pingが使えない。セキュリティグループのインバウンドにおいてicmpを開けておく設定をしておくこと。

本題terraformで実行するとどうなるのか??

ちょうどやりたいことが以下の説明にあった。
AWSをテラフォーミングする会(Terraformハンズオン)

*注意点 上記の資料は結構昔のものなんだけれども、terraformは下方互換性がない。したがって自分のバージョンは0.12.5なんだけれども、このバージョンでは動かなかった。

修正点を以下に記す。

tags {} ではなくtags = {}と記述する

他にも良い記事があった。

SSHでアクセスできるEC2インスタンスを構築する - togatttiのエンジニアメモ

TerraformでVPC・EC2インスタンスを構築してssh接続する - Qiita

terraformで実現するための留意点

マネージドコンソールを使ってEC2を立ち上げる場合を考えてみると、EC2インスタンスを生成する時に.pemダウンロードすることになる。正確に言えば、新しい.pemをダウンロードするか、既存の.pemをec2インスタンスに登録する。そして、この.pemを使ってssh接続することになる。

そしてterraformを使ってec2を立ち上げるときのことを考えるとどうだろうか。 ec2を立ち上げるときに.pemをブラウザを通してダウンロードする事ができるだろうか。まずCUIで操作するのでそんな事は考えられない。したがってマネージドコンソールで自動的に行われていた.pemに関する作業は、自分で行わなければならないことがわかるだろう。

したがってマネージドコンソールで色んな要素を作成してEC2を組み立てること + ssh接続に関する設定を加えると想像することは明らかだ。以下の作業ではssh接続するためのkeyを生成すること、ec2に当該公開鍵を登録する操作ec2インスタンスを立ち上げる作業に加えている。

手順(全体)

  1. terraformを使えるようにセットアップする
  2. ディレクトリを準備する
  3. sshで接続するためのkeyを生成する
  4. ファイルを作成する
  5. terraform applyでec2を立ち上げssh接続を行う

terraformを使えるようにセットアップする

特に説明事項はない。brewでインストールして使う。

ディレクトリを準備する

$ mkdir example

sshで接続するためのkeyを生成する

$ cd example
$ ssh-keygen -t rsa -f id_rsa_ec2

ファイルを作成する

$ mkdir ec2_ssh
$ cd ec2_ssh
$ touch ec2.tf
$ vi  ec2.tf
# ec2_ssh/ec2.tfの内容

# EC2 Instance

resource "aws_instance" "example" {
  ami                    = "ami-0873b46c45c11058d"
  vpc_security_group_ids = [aws_security_group.example.id]
  subnet_id              = aws_subnet.example.id
  key_name               = "id_rsa_ec2" #EC2にKeyPairを登録
  instance_type          = "t2.micro"

  tags = {
    Name = "example"
  }
}


# Key Pair

resource "aws_key_pair" "example" {
  key_name   = "id_rsa_ec2"
  public_key = file("../id_rsa_ec2.pub") # 先程`ssh-keygen`コマンドで作成した公開鍵を指定
}
touch vpc.tf
# ec2_ssh/vpc.tfの内容


# VPC

resource "aws_vpc" "example" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true # DNS解決を有効化
  enable_dns_hostnames = true # DNSホスト名を有効化

  tags = {
    Name = "example"
  }
}



# Subnet

resource "aws_subnet" "example" {
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-west-2a"
  vpc_id            = aws_vpc.example.id

  # trueにするとインスタンスにパブリックIPアドレスを自動的に割り当ててくれる
  map_public_ip_on_launch = true

  tags = {
    Name = "example"
  }
}




# Internet Gateway

resource "aws_internet_gateway" "example" {
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example"
  }
}



# Route Table

resource "aws_route_table" "example" {
  vpc_id = aws_vpc.example.id

  tags = {
    Name = "example"
  }
}

resource "aws_route" "example" {
  gateway_id             = aws_internet_gateway.example.id
  route_table_id         = aws_route_table.example.id
  destination_cidr_block = "0.0.0.0/0"
}

resource "aws_route_table_association" "example" {
  subnet_id      = aws_subnet.example.id
  route_table_id = aws_route_table.example.id
}



# Security Group

resource "aws_security_group" "example" {
  vpc_id = aws_vpc.example.id
  name   = "example"

  tags = {
    Name = "example"
  }
}

# インバウンドルール(ssh接続用)
resource "aws_security_group_rule" "in_ssh" {
  security_group_id = aws_security_group.example.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
}

# インバウンドルール(pingコマンド用)
resource "aws_security_group_rule" "in_icmp" {
  security_group_id = aws_security_group.example.id
  type              = "ingress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = -1
  to_port           = -1
  protocol          = "icmp"
}

# アウトバウンドルール(全開放)
resource "aws_security_group_rule" "out_all" {
  security_group_id = aws_security_group.example.id
  type              = "egress"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
}

terraform applyでec2を立ち上げssh接続を行う

terraform apply
# yesを実行する
# EC2が立ち上がったら
# インスタンスのパブリックIPをチェックする

ssh -i id_rsa_ec2 ec2-user@インスタンスのパブリックIP
#EC2インスタンスを破棄する
terraform destroy

djangoのcontextをjavascriptに渡す際に困る場合がある件

大した内容ではないので問題点と対処法を軽く書く。

djangoのcontextに格納したデータをjavascriptにわたす方法

  1. 最初にviewにてデータをcontextに格納する
  2. テンプレートにてjavascriptを走らせ、contextのデータを受け取る
def example(self, request, *args, **kwargs):
    context = {}
    context["data"] = "my_data"
    return render(request, "hoe/hoge.html", context)

テンプレートのscriptタグ内でdataをを取り出す。

<script>
var data_of_django = "{{ data }}";
console.log("{{ data }}")
</script>

こんな感じでscriptタグ内でdjangoのcontextデータを取り出し加工したりして使っていくのが基本。

問題となるパータン

モデルの属性としてTextAreaフィールドを設定している場合がある。 TextAreaは改行もデータとして保存できるが、この改行がjavascriptではエラーの原因になってしまう。

対処法

javascriptに渡すデータをjsonに加工すること、またテンプレートでは"|safe"を使って取り出すこと。この2点で対処できる。

import json
def example(self, request, *args, **kwargs):
    context = {}
    context["data"] = json.dumps("my_data") #ココ
    return render(request, "hoe/hoge.html", context)
<script>
var data_of_django = "{{ data|safe }}";
console.log("{{ data|safe }}")
</script>

leafletを使って以下が表示される。"This content should also be served over HTTPS."

問題は何なのか

leafletを使っていて以下の内容がconsoleに表示される事になった。

Mixed Content: The page at '<URL>' was loaded over HTTPS, but requested an insecure image '<URL>'. 
This content should also be served over HTTPS.

これは何を示しているかというと、表示されたページがHTTPS通信でページが表示されているにも関わらず、安全ではないHTTP通信で通信されている。 この部分についてもHTTPSで配信するべきだ。って旨である。
で、該当するURLは"http://c.tile.osm.org/13/2014/3754.png"であった。
osm.orgからの配信内容である。 自分の場合leafletを使ってopen street mapを配信するときにこれが表示される事がわかった。

これについてhttps化してosmを配信する資料があった。

osmhttps化して配信する資料

Streaming Leaflet Tiles over HTTPS / SSL · Issue #3186 · Leaflet/Leaflet · GitHub

資料の概要

L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

上のコードを下のコードに変更する。

L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

要するに"http://{s}.tile.osm.org/{z}/{x}/{y}.png"の部分を'https://{s}.tile.osm.org/{z}/{x}/{y}.png'に変更するだけといったもの。

自分のコードを直してみる

自分のコードを確認してみると、、、

var map = L.map('map_leaflet', { center: [lat, lon],zoom: 13})
                        .addLayer(L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png'));

確かにhttpから配信する仕組みになっている。これをhttpsに変更してみようと思う。

'https://{s}.tile.osm.org/{z}/{x}/{y}.png'に変更したところうまく行った。

他にも 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'で成功している人もいるみたいだけれども自分の場合はhttps://{s}.tile.osm.org/{z}/{x}/{y}.png'でうまく行った。

またleafletのcss,javascriptを直接ダウンロードしてnginxから直接配信する方法でもhttps化できるという意見もあった。

supervisorについてメモ

ドキュメント

Supervisor: A Process Control System — Supervisor 4.2.0 documentation

supervisorは何なのか?

これはプロセス管理のツールである。

fabricを使った自動デプロイスクリプトを書いては見たもののgunicornのプロセスを切ってから新たに立ち上げることにたいして思うように制御することができなかった。そこでsupervisorを使う事になった。

supervisorのイメージ

supervisorの構成はドキュメントでは以下の構成とされている。

supervisord
supervisorctl
Web Server
XML-RPC Interface

supervisor-components

まだよく分かっていない状態でこれらの役割を敢えていうとすると、supervisordはクライアントから命令を受けるためのサーバーでありsupervisorctlはそのサーバーに命令を送るためのクライアントである。このクライアントはshellのようなインターフェースをもって命令を送ることになる。Web Serverはwebのコントロールパネルから制御するものなのかと仮設を立てているが使っていないのでよくわからない。XML-RPC Interfaceは全くわからない。
自分が言いたいことはsupervisordとsupervisorctlさえ使えばdjangoのアプリケーションのプロセス管理ができるということだ。

djangoのプロセス制御をどのように行ったか

上で説明したようにsupervisordとsupervisorctlさえ使えばプロセス管理ができる。両者を使うには諸設定ファイルが必要なのはもちろん、linuxでは標準実装されていないのでインストールが必要になる。またsupervisorの立ち上げ、supervisorctlを使ってプロセスをkill、アプリケーションの立ち上げを行う。これらをできるだけ順を追って再現できるように振り返りたい。

0. 前提

  • supervisor 4.2.0
  • centos7

1. インストール

supervisorをインストールする。これはpipを使う方法とepelからyum intastallみたいな形でインストールすることもできるようだ。自分はpipからインストールした。 popのほうがより新しいバージョンのsupervisorを使うことができるからだ。

pip install supervisor

installing

2. 設定ファイルを作成

supervisordとsupervisorctlは設定ファイルによって制御されている。この設定ファイルは同一のファイルから両者を制御することもできるし、supervisorctlのコントロールしたいプロセス毎に作成し、それとsupervisordの設定ファイルということもできる。詳しいことは設定ファイルの編集で説明する。

pipでインストールしただけでは設定ファイルが存在しないので設定ファイルを作成する。
設定ファイルは雛形が存在しており、コマンドを使ってその雛形を呼び出し設定ファイルとする。

#supervisorの設定ファイルを/etc配下に作成する
sudo touch  /etc/supervisord.conf
sudo echo_supervisord_conf > /etc/supervisord.conf

echo_supervisord_confのコマンドはsupervisorをインストールすると使えるようになっており、この中に設定ファイルの雛形が書いてある。この中身を設定ファイルにコピーする流れになる。

また設定ファイル名は上記のようにsupervisord.confが望ましいし、当該ファイルの配置場所も/etc/以下に配置するのが望ましい。 それは基本的に-cオプション(設定ファイルの指定)を付さないでコマンドを入力すると、supervisorはsupervisord.confという設定ファイルを読み込もうとする仕様であるからだ。また、設定ファイルの配置する場所も-cオプションがなければ以下のディレクトリから設定ファイルを探す仕様になっているからだ。

../etc/supervisord.conf (Relative to the executable)
../supervisord.conf (Relative to the executable)
$CWD/supervisord.conf
$CWD/etc/supervisord.conf
/etc/supervisord.conf
/etc/supervisor/supervisord.conf (since Supervisor 3.3.0)

configuration-file

3. 設定ファイルの編集

作成した/etc/supervisord.confに手を加えるとsupervisordとsupervisorctlが使えるようになる。試してはないので確信が持てないがsupervisord.confにsupervisordとsupervisorctlの両方の内容を記述して動かすことができる。
自分の場合はsupervisordについては/etc/supervisord.confに記述し、supervisorctlで管理したいプロセスは別途設定ファイルを作成し、 /etc/supervisord.confから当該設定ファイルの読み込みませる仕組みにした。したがっていまから行うのは/etc/supervisord.confの編集とsupervisorctlで管理するプロセスに関わる設定ファイルの作成である。

ⅰ. /etc/supervisord.confの編集

supervisordに関する設定内容。ここは設定すると言ってるが全く触らなくてよかった。雛形のママである。
このファイルだとlogfileやpidfileが/tmp以下に作成されるので何らかのエラーが生じた場合は/tmp以下のlogを閲覧することになる。ココは任意に変更すると良いだろう。

[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           ; # of main logfile backups; 0 means none, 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
silent=false                 ; no logs to stdout if true; default false
minfds=1024                  ; min. avail startup file descriptors; default 1024
minprocs=200                 ; min. avail process descriptors;default 200
;umask=022                   ; process file creation umask; default 022
;user=supervisord            ; setuid to this UNIX account at startup; recommended if root
;identifier=supervisor       ; supervisord identifier, default is 'supervisor'
;directory=/tmp              ; default is not to cd during start
;nocleanup=true              ; don't clean up tempfiles at start; default false
;childlogdir=/tmp            ; 'AUTO' child log dir, default $TEMP
;environment=KEY="value"     ; key value pairs to add to environment
;strip_ansi=false            ; strip ansi escape codes in logs; def. false

; The rpcinterface:supervisor section must remain in the config file for
; RPC (supervisorctl/web interface) to work.  Additional interfaces may be
; added by defining them in separate [rpcinterface:x] sections.

同ファイルの最下部に[include]がある。これを編集(追記)する。

[include]
files = /etc/supervisord.d/*.conf ;(追記) 起動するプロセスのconfファイルの配置場所
;files = relative/directory/*.ini

include-section-settings

ⅱ. プロセスのconfファイルを作成

プロセスのconfファイルを配置するディレクトリを作成。

sudo mkdir /etc/supervisord.d

プロセスのconfファイルを作成

sudo touch /etc/supervisord.d/django_project.conf

プロセスのconfファイルの編集

sudo vi /etc/supervisord.d/django_project.conf

で以下のように記述する。

[program:django_project] ;この値を引数としてsupervisorctlを使う
directory=/home/user_name/src
command=gunicorn config.wsgi --bind localhost:8000 --env DJANGO_SETTINGS_MODULE=config.prod_settings --daemon config.wsgi:application
numprocs=1
autostart=true
autorestart=true
user=user_name ;デプロイするユーザー名
redirect_stderr=true

4. supervisordの起動とプロセスの実行

supervisorの起動する
supervisord
設定ファイルの読み込み
supervisorctl reread
プロセスのリスタート
supervisorctl restart django_project

一発でうまく起動することが自分はできなかった。自分の場合はすでに8000番ポートを使ってアプリケーションを起動していた。うまくイカない場合はすでに起動しているdjangoのプロジェクトのプロセスをkillしてから実行してみるとよい。

sudo lsof -i:8000

pidの番号をkillする

kill -9 番号

Djangoのformをより良く見せるために(改訂版)

Djangoにおいてformウィジェットの理解を深めない限り、forms.ModelFormやforms.Formの利用したとて、フォームを使った画面が残念な結果になってしまう。

見た目の良いフォーム画面を作るには

これらの方法が考えられるがメリット、デメリットが存在する。 これらのメリット・デメリットを考えてみたい。

そもそもウィジェットとは?

現在自分の中ではウィジェットとはformだったりボタン等のユーザーインターフェースのことを指すと考えている。

htmlファイルを自ら作成して好みのformを作成する方法

これはテンプレートにformタグと共にインプットタグ等を書いていく方法で、class属性を思うままに書くことができるので自由に作ることができる。
しかしながらdjangoに備わっているインプットデータのバリデーション機能を享受できなくなるデメリットがある。自分でバリデーション機能も実装するのは手間なので避けるべき方法だと思う。

ModelFormやFormのウィジェットをforms.pyにて編集する方法

関連ドキュメント

https://docs.djangoproject.com/ja/2.2/topics/forms/
https://docs.djangoproject.com/ja/2.2/ref/forms/widgets/

例えばBootstrap4のform-controlを反映させたい場合以下のようになる。

class CommentModelForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ("name","url","comment")
        
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            self.field["name"].widget.attrs["class"] = "form-control"
            self.field["url"].widget.attrs["class"] = "form-control"
            self.field["comment"].widget.attrs["class"] = "form-control"
    

その他ウィジェットを編集する方法は以下のようになる。なお、参考資料は以下である。 https://docs.djangoproject.com/ja/2.2/ref/forms/api/#accessing-the-fields-from-the-form

def __init__(self, *args, **kwargs):
   super().__init__(*args, **kwargs)
   for field in self.fields.values():
        self.field["name"].required = True  #必須項目のフォームに変更する
        self.field["url"].label = "URL"  #ユーザ視点でフォームの入力名が変わる。
        self.field["comment"].widget.attrs["placeholder"] = "コメントを書いてください。"

forms.pyのinitwidgetを編集する方法である。これを使うとformのwidgetの属性値を管理するのが楽になる。forms.pyを直接いじれない場面に遭遇するといじるために手間がかかる。またファイルは違えどviews.pyで編集する方法もこの方法に含まれる。

Javascriptを使ってformを改善させる方法

上記の方法はforms.pyにおいてformのインスタンス初期化時に属性をセットする方法である。 サードパーティのライブラリを使う場合に問題が顕在化する。例えばdjango-allauthではログイン等の画面も準備してくれるが、そのフォームを編集する際にforms.pyをいじることが多少難しくなることだ。実際いじることはできるが少し複雑になってしまうので構成を共有して理解するには難しいと思われる。
こちらの記事の一部にformの編集の仕方を書いている。
django-allauth : テンプレートのカスタマイズ - diadia

このような場合にはjavascriptを使ってDOMを操作することが良い思う。 共通のformを利用する場合にはincludeを挿入すればよいだろうし、コンポーネント化できるので積極的に使って良い方法だと思われる。問題点はjavascriptの知識が要求されることである。
Vue.jsとか他のフレームワークを使っている場合DOM操作がバッティングする恐れがあり、それらがどのように反応するかは検証する必要がある。自分の場合それがどのように反応するかは分かっていないのでそこをはっきりさせるのにコストがかかる。

window.addEventListener('load', function() {
    var email_form = document.getElementById("id_email")
    email_form.setAttribute('class', 'form-control');
})

サードパーティライブラリを使用する方法

django-widget-tweaksというライブラリが存在する。
https://python.keicode.com/django/form-add-class.php これを使えばテンプレートタグに追加したい属性値を直接記述できるようになる。サードパーティライブラリなのでメンテナンスされるかが外部に依存する事になってしまう。

{% load widget_tweaks %}
{{form.username|add_class:"form-control"}}

結論

個人的にはforms.pyのインスタンスの初期化で各ウィジェットを調整し、それが難しい場合にはjavascriptを使って調整する方法を取るのが望ましいと思う。

django ページネーションについて(改訂版)

ページネーションについて

ページネーションは一覧表示させるオブジェクトを1ページ内にどれだけ分割して表示させるか。それに関わる技術のことのようだ。

関連ドキュメント

ページネーション | Django ドキュメント | Django

メモ

djangoにおけるページネーションに関わるオブジェクトは2つある。

  1. Paginator オブジェクト
  2. Page オブジェクト

このオブジェクトの役割は具体的にはなんなのか?
Paginator オブジェクトはどんなページネーションにするかを定めるために使うのと、そのオブジェクトを使ってページを表示することができる(もちろん他にも)。
Page オブジェクトはあるページに該当するデータを参照したり、その隣(前のページや後のページ)が存在するか、その隣のページを参照するような役割をもつ。

1ページ当たりいくつのオブジェクトを描画するかを定めるのがPaginatorの役割で、それはsettings.pyでページネーションを定めるのではなくviewsの各ロジックに組み込んで使用する。 またPageオブジェクトはフロントエンド側で使いやすいと思われる。具体的には隣のページがあればページを繰るアイコンを表示させるとかクリックすることで該当ページの内容を表示させるとか。 もちろんPageオブジェクトをテンプレートで使いたいのでviewsでPageオブジェクトを生成しcontextに格納することを想定される。両オブジェクトはともにviewsで使用されることか2つのオブジェクトを組み合わせた関数を作ってしまうと便利かも。

以前の記事

ページネーションの必要素は2要素のようだ。表示するべきリストオブジェクトの作成とのページを繰る関係のオブジェクトである。で前者はPageクラスインスタンスにobject_list属性で表現できるようだ。

ページネーション | Django ドキュメント | Django

後者については同じくPageクラスインスタンスに属性やメソッド使ったりすると表現できるようだ 。

ページネーション | Django ドキュメント | Django

page_obj.object_listは普段object_listをhtml上に表現するように扱えばいいので特に問題はないだろう。page_obj.numberについて考える。まずpage_obj.numberはどこに使うか?という問題である。言うまでもなくviews.pyのrequest.GETに関するコードの流れの最終着地点はcontextに追加し、render関数を介してhtml上に表示させることだ。だからpage_obj.numberはhtmlに表現される。htmlの下部の"前へ","1","2","3","次へ"というものだ。page_obj.numberはここに使われる。またpage_objには以下のメソッドがある。これらを使うと前へ、とか"3"を表現できるようになりそうだ。

Page オブジェクトメソッド 動作
page_obj.has_next() 次のページが存在する時Trueを返す。
page_obj.has_previous() 前のページが存在する時Trueを返す。
page_obj.has__other_pages() 次のページまたは前のページが存在する時Trueを返す。
page_obj.has_next_number() 次のページ数を返す。次のページが存在しない場合はInvalidPage例外を起こす。
page_obj.start_index() ページ上の最初のオブジェクトに対する、1から始まるインデックスを返します。これは、ページネータのリストに含まれる全オブジェクトに対するインデックスです。たとえば、5個のオブジェクトのリストを各ページ2オブジェクトでページネーションしている場合、2ページ目の start_index() は 3 を返すでしょう。
page_obj.end_index() ページ上の最後のオブジェクトに対する、1から始まるインデックスを返します。これは、ページネータのリストに含まれる全オブジェクトに対するインデックスです。たとえば、5個のオブジェクトのリストを各ページ2オブジェクトでページネーションしている場合、2ページ目の end_index() は 4 を返すでしょう。

だからhtmlではpage_obj.object_listの使い方としては、

{% for obj in page_obj.object_list %}

{{ obj }}

{% endfor %}

ページを繰る関係で言えばhtmlでは以下のように表現できる。

{%for n in  page_obj.paginator.page_range %}

<a href="?page={{ n }}">{{ n }}</a>

{% endfor %}

page_objの作り方

page_objはPageクラスのインスタンスである。しかしながらpage_obj=Page()と作るわけではないようだ。作り方はpage_obj = pagenator_obj.page(number)で作る。pagenator_obj=Pagenator()で作成する。

cdnでvuetifyを使うために

軽く要点を残す。

djangoでincludeを使ってコンポーネント化できたのでテンプレートの見通しが良くなった。
cdnを使ってコンポーネント化するとvuecliを入れる前にどんな感じでvuetifyや単一ファイルコンポーネントが使えるか確認できる。(もちろん全く同じではない。)

完成形イメージ

<body>
<my_template/>

{% include "my_template_vuejs.html" %}
</body>

"my_template_vuejs.html"でmy_templateタグの定義を行うとともに、vueの初期化でmy_templateタグを使えるように書く。そしてmy_templateタグを表示させたいところに配置する。

必要なこと

1 vue.jsが起動するテンプレートタグを設定する。

<body>
<my_template/> #これ
</body>

2 "my_template"タグの定義を書く

cdnでテンプレートを定義するときに注意しなければならないことがある。それは必ずscriptタグから書き始めること、templateタグを内部で書かないことである。特にtemplateタグを書いてしまうとエラーとなってしまうので注意が必要。 またこのような書き方はインラインテンプレート記法と呼ぶようだ。Vue.js初心者向け:インラインテンプレートから単一ファイルコンポーネントの使い方まで - Qiita

touch hoge/my_template_vuejs.html
<script type="text/x-template" id="m_temp">

<p>自分の書きたいコンポーネントを書く。
ここにvuetifyのテンプレートを直接書き始めても正常に読み込まれる。</p>

</script>

3 同一ファイル内でvueの初期化を実施する

<script>

Vue.component("my_template", #呼び出したいテンプレート名
{ 
    template: "#m_temp", #上で定めたコンポーネントのid

});

new Vue({
    el: "m_temp",
    vuetify: new Vuetify() #Vuetifyを使う場合これを追加する
})
</script>

あとはインクルードで呼び出せば使える。

cdnコンポーネント化する資料はココにもあった。こっちのが書き方が明快かも CDNで始めるVue.jsコンポーネント③ - Qiita

nginxで403が返されるときに対応したこと

サーバー上に新しくディレクトリを作成し、そこを起点にgithubからpullを行いデプロイした際にうまく作動しなかった。

環境

centos7
nginx
porstgresql
django

症状:

  1. home画面のhtmlはサーバーから配信される。
  2. home画面の静的ファイルの配信がうまく行っていない。
  3. 配信がうまく行かなかった静的ファイルに直接アクセスする(新しいタブで画像を開く)場合403が返される。

判断:

home画面のhtmlはサーバーから配信される。 -->
webサーバーとWebApplicationSerer(django)は正常に作動していると可能性が高い。

home画面の静的ファイルの配信がうまく行っていない。 -->
webサーバー(nginx)から静的ファイルを送信する際に問題がある。

(新しいタブで画像を開く)場合403が返される。-->
nginxが静的ファイルにアクセスするが権限に関して問題がある可能性を示唆している。

対応したこと

  1. "nginx 403"で検索をかけ、以下の記事を探し当てた。 Nginx で 403 Forbidden エラーが出るときのチェック項目|まくろぐ
  2. ドキュメントルートまでに実行権限があるかチェックした。
  3. うまく配信されなかった静的ファイルの読み取り権限があるかチェックした。

なお、権限チェックに関してはlsコマンドを使った。

ls -l (対象ディレクトリ)

ドキュメントルート(静的ファイルを配信するために静的ファイルを集めたディレクトリ)までの実行権限を調べたところ見事に実行権限がなかった。具体的にはドキュメントリート自体には実行権限xが存在していたが、親のディレクトリとその親ディレクトリにはxの権限が欠如していた。 rootからドキュメントルートまで下記のコマンドを実行した。

chmod +x (対象のディレクトリ)

問題のあった静的ファイルの読み取り権限rはあったのでこれに関して特に行うことはなかった。

そして再度nginxの起動を行った。

systemctl restart nginx

結果として正常に静的ファイルを表示させることができた。

この件から得られた知見

nginxがファイルを配信する前提としてドキュメントルートまでの実行権限が必要なこと、配信する静的ファイルに読み取り権限が必要なことを学んだ。またnginxで403が出る場合とその他のエラーが出る場合を経験したけど、そもそもhome画面が表示されない場面に出くわした。このときは静的ファイルがドキュメントルートに存在していない場合があった。

だからnginxの配信するファイルの存在があるかないか ?
無い --> 画面自体が表示されない。
ある --> 権限に問題がある。

今の所こんな感じの原因を推測できる

AWS学習メモ

知識の整理の方法をよく考えたい。

実現したい実装に対して、概念による実装方法があり、具体的な実装方法がある。

具体的な実装方法はコンソールによる具体的な実装方法とteraformによる実装方法である。これらの棲み分けを意識して分けてメモをしていきたい。

EC2のパブリックIPアドレスがほしいときは、ec2インスタンス生成時に"自動割当パブリックIP"という項を有効化しなければならない。デフォルトだと有効可になってないので注意。そしてネットワーク内部で通信するときにはネットワーク・インターフェースという項で自分の好きなプライベートIPを設定する事ができる。

VPC: Virtual Private Cloud

ネットワークの基盤を作るサービス

awsマネジメントコンソールの場合

VPCを作成するときにはVPCサービスから選択してVPCを作成する。

Terraformの場合

resource"aws_vpc"
"example"{
  cidr_block="10.0.0.0/16"
  enable_dns_support=true
  enable_dns_hostnames=true
  
  tags={
    Name="example"
  }
}

VPC内にサブネットを作成するけれども、これはどのようにするか?サブネットはVPCサービスの項目の一つであるので、VPCダッシュボードから作成することになる。サブネット作成時の留意点は、どのVPCと紐付けるかを確認することと、サブネットのCIDRの値を定めることである。

ルーティングの設定

  1. 大きく分けて2つある。インターネットゲートウェイを作成し、それをVPCにアタッチすること。
  2. ルートテーブルを作成し、パブリックサブネットに紐付ける。

インターネットゲートウェイもルートテーブルもVPCダッシュボードの項目の一つとして存在している。各項目を選んで各々作成する運びとなる。

要素 割り当てる、アタッチ対象
サブネット VPC
インターネットゲートウェイ VPC
ルートテーブル サブネット

|セキュリティグループ|EC2インスタンス|

要素 何を設定スべき
VPC ネットワークの構成
サブネット サブネットの領域
ルートテーブル インターネットに接続したい場合はこちらにインターネットゲートウェイの情報を登録する
セキュリティグループ IPとポートを制限しているが、sshやhttp,DBの接続用にポートを開けること考えなければならない。
fdfs vdvd
要素 どう設定するか
ddd df

EBS

EC2のストレージ扱いっぽい。EC2を停止していてもこの部分に関しては課金され続ける。

セキュリティグループ

セキュリティグループってのはファイアウォールに当たる言葉のようだ。

EC2接続方法

macの場合はssh接続を行う。ダウンロードしたキーを使ってssh接続を行う。 注意点はログインユーザー名をec2-userとしてログインを実行する。

chmod 600 ~/Desktop/aws-keypair.pem

ssh -i ~/Desktop/aws-keypair.pem ec2-user@54.245.191.191 -p 22

アパッチのインストール、セッティング

インストール
sudo yum update -y

sudo yum install httpd -y
起動
sudo systemctl start httpd
sudo systemctl enable httpd
sudo systemctl status httpd

RDSについて

これもAWSのサービスの一つ。データベース関連のものでpostgresql, mysql, mariadb, oracle, sql server等を使うことができる。 DBをセッティングする方法としてオンプレミス、onEC2 、RDSの3通りが考えられる。onEC2とは、さくらVPCのサーバー上にDBをインストールさせるのと同じようにEC2インスタンス上にDBを設けることだ。

RDSを始める

まずRDSは可用性を高めるためにサブネットを2つ準備しなくてはならない。したがって、VPCのメニューからサブネットを2つ作成する。 次にRDSに接続するためのセキュリティグループをRDS作成時に求められる。これも作成しなければならない。 あとwebサーバーとDBサーバーを分ける場合にはRDSに接続するためにwebサーバー側にDBのクライアントをインストールする必要がある。

またRDSもEC2と同様従量課金制なので使用しないときはRDSを停止することが望ましい。
[速報] RDSインスタンスの起動/停止 | Developers.IO

MYSQL

インストール

sudo yum -y install mysql

接続

mysql -h (endpoint: RDSに書いてある) -u (username: RDS作成時に定めた値) -p

このあとパスワードを入力して認証されれば、RDSに接続できたことになる。 またssh接続する事はできない。

ユーザー作成

CREATE USER '(ユーザー名)'@'%' IDENTIFIED BY 'password';

作成したユーザーにDBへの権限を付与する

GRANT ALL ON (DB名).*  TO '(ユーザー名)'@'%';

与えた権限を反映させる

FLUSH PRIVILEGES;
SELECT user, host FROM mysql.user;

terraformのリソースタイプ(確認している限り)

  1. resource"aws_instance"
  2. resource"aws_security_group"
  3. resource"aws_vpc"
  4. resource"aws_subnet"
  5. resource"aws_internet_gateway"
  6. resource"aws_route_table"
  7. resource"aws_route"
  8. resource"aws_route_table_association"
  9. resource"aws_eip"

RDSについて

料金の請求はRDSインスタンスを削除していても請求されることがあるから注意。必ず削除したRDSに関連したスナップショットも削除すること。これで請求されなくなる。