投稿者: sumito.tsukada

  • HAproxyでHR構成を組む on AWS

    HAproxyでHR構成を組む on AWS

    はじめに

    DBサーバへ接続する際、readレプリカを複数台作ることが一般的だが、readレプリカに対してバランシングはインフラの設計ポイントとなる。auroraであれば自動でreadレプリカを作ってくれるのでその考慮は要らないがレガシーなシステムを運用している場合それは使えない。今回はAWSで組むこととする。

    やりたいこと

    多くのプログラムはそうであろう構成。

    プログラムは更新用(write)と参照用(read)で接続先を変え、
    データベースはMasterとSlaveでレプリケーションをしている。

    Slaveは今後増える可能性もあるのに加え、Slaveが1台ダウンしてもサービスが稼働できるようにするには、クラスタリング構成をする必要がある。

    クラスタリングソフトの検討

    名前考察
    keepalived名前解決不可能。また、ググっても実績なし
    HAProxyAmazon linuxでyumでのversionが古い(1.5.2)。v1.6の resolvers オプションを使ってVPCのnameserverを参照するようにし、A Recordの変更に追従させる事が可能だが、ソースからのinstallになってしまう
    MySQL Routerアルファ版 本番利用 非推奨
    Consul by HashiCorpググっても出ない
    ProxySQLAmazonLinux非対応

    keppalivedはAWSでは難しいようだ。

    今回はHA proxyで実現することにした。

    対応

    Amazon linuxの上にHAproxyを使いクラスタリングを実現する。

     

    HAproxyのインストール

    amazon linuxの場合、yumでinstallしようとすると、versionが古い。

    そのため、sourceでインストールすることにした

    yum install -y gcc
    cd /usr/local/src/
    wget http://www.haproxy.org/download/1.8/src/haproxy-1.8.8.tar.gz
    pwd
    tar xvfz haproxy-1.8.8.tar.gz
    ls -ltr
    cd haproxy-1.8.8
    make TARGET=generic
    make install
    

    HAproxyの設定

    設定は以下の通り

    $ cat /etc/haproxy/haproxy.cfg 
    global
        log         tsukada01 local2
    
        chroot      /var/lib/haproxy
        pidfile     /var/run/haproxy.pid
        maxconn     4096
        user        haproxy
        group       haproxy
        daemon
        stats socket /var/lib/haproxy/stats
        stats maxconn 1
        stats timeout 120s
    
    resolvers awsvpc
        nameserver vpc 172.20.0.2:53
    
    defaults
        mode        tcp
        log         global
        retries     3
        timeout     connect 10s
        timeout     client 1m
        timeout     server 1m
        timeout     check 10s
        maxconn     512
    
    listen mysql
        bind tsukada01:3306
        mode tcp
        option mysql-check user haproxy_check
    
        balance roundrobin
    
        option          log-health-checks
    
        server read01 dbserver01:3306 check port 3306 resolvers awsvpc inter 2000 fall 3
        server read02 dbserver02:3306 check port 3306 resolvers awsvpc inter 2000 fall 3

    もう片方のサーバは今の通りに設定した

    $ cat /etc/haproxy/haproxy.cfg
    global
        log         tsukada02 local2
    
        chroot      /var/lib/haproxy
        pidfile     /var/run/haproxy.pid
        maxconn     4096
        user        haproxy
        group       haproxy
        daemon
        stats socket /var/lib/haproxy/stats
        stats maxconn 1
        stats timeout 120s
    
    resolvers awsvpc
        nameserver vpc 172.20.0.2:53
    
    defaults
        mode        tcp
        log         global
        retries     3
        timeout     connect 10s
        timeout     client 1m
        timeout     server 1m
        timeout     check 10s
        maxconn     512
    
    listen mysql
        bind tsukada02:3306
        mode tcp
        option mysql-check user haproxy_check
    
        balance roundrobin
    
        option          log-health-checks
    
        server read01 dbserver01:3306 check port 3306 resolvers awsvpc inter 2000 fall 3
        server read02 dbserver02:3306 check port 3306 resolvers awsvpc inter 2000 fall 3

    ほとんど同じように見えるので差分をまとめると、以下のような違いだ

    これで

    • tsukada001の3306ポート
    • tsukada002の3306ポート

      に接続すると、dbserver01,dbserver02へラウンドロビンで接続されるようになった

    しかし、これでは当然tsukada001、tsukada002自体で障害が起きる可能性もある。

    ALBでバランシング

    tsukada001:3306、tsukada002:3306をALBに登録しラウンドロビンで登録した。

    これによりALBのエンドポイントが払い出され、それを各プログラムが参照するような形にして可用性を担保した。

    振り返って見て思うこと

    レガシーなDBでこの構成を組もうとすると、できないことはないが、登場人物がやたら多い。一番なのはauroraなどに移行し、本当に必要なことのみに集中できれば最高だ。

     

     

  • SSL証明書の有効期限を外部から確認する

    SSL証明書の有効期限を外部から確認する

    はじめに

    近年SSL対応したページが当たり前になってきた。AWSであればAWS Certificate Manager (ACM)の機能で自動で証明書を更新してくれるが、念には念を期限くらいは監視しておきたい。また、Let’s encryptを使う環境だと3ヶ月ごとに期限切れになるので、有効期限を監視するのはとても重要だ。外部から証明書の有効期限を確認するコマンドを紹介。

    実施コマンド

    Linuxの場合

    実は非常に簡単に確認ができる

    Linuxの場合はだいたい標準でOpenSSLがインストールされているので、そのコマンドで確認ができる

    openssl s_client -connect tsukada.sumito.jp:443 < /dev/null 2> /dev/null | openssl x509 -text | grep "Not "
                Not Before: Mar 20 09:14:22 2018 GMT
                Not After : Mar 20 09:14:22 2019 GMT

    URLの部分を変えれば、どの環境でも対応できる

    Macの場合

    brewコマンドでインストール

    brew tap genkiroid/homebrew-cert
    brew install cert

    あとはcertコマンドで後ろに確認したいサイトのURLを記載するのみ。シンプル!

    cert tsukada.sumito.jp
    DomainName: tsukada.sumito.jp
    IP:         35.187.235.61
    Issuer:     Let's Encrypt Authority X3
    NotBefore:  2018-07-18 16:45:33 +0900 JST
    NotAfter:   2018-10-16 16:45:33 +0900 JST
    CommonName: tsukada.sumito.jp
    SANs:       [tsukada.sumito.jp]
    Error:      

     

     

  • Enhanced networking with the Elastic Network Adapter (ENA) is required for the ‘t3.small’ instance type. Ensure that your instance ‘i-xxxxxxxx’ is enabled for ENA.

    Enhanced networking with the Elastic Network Adapter (ENA) is required for the ‘t3.small’ instance type. Ensure that your instance ‘i-xxxxxxxx’ is enabled for ENA.

    はじめに

    AWSでT3インスタンスがリリースされた。t2.smallを多く使っていたので、似たようなスペックを探したところ、t3.smallインスタンスというのを見つけた。

    t2.smallではCPUが1coreだったのが、t3.smallでは2coreになっているだけでなく安くなっている!

     

    t3.small CPU:2core MEM:2G 0.0272USD/hour
    t2.small CPU:1core MEM:2G 0.0304USD/hour

    https://aws.amazon.com/ec2/pricing/on-demand/?nc1=h_ls

    まさに「うまい、やすい、はやい」というやつだ。インスタンスタイプを変更しない理由はないので、OSを停止できるものから止め、インスタンスタイプを変更していったが、wordpressが動いているkusanagiサーバだけが変更できなかった。

    Enhanced networking with the Elastic Network Adapter (ENA) is required for the ‘t3.small’ instance type. Ensure that your instance ‘i-xxxxxxxx’ is enabled for ENA. が発生

    kusanagiのインスタンスタイプは変更できたが、起動時にエラーになった。

    どうやら Elastic Network Adapterというのがインスタンスで有効になっていないのが原因のようだ。

    では、新しくサーバを立てた場合はどうなるか確認したところ、t3インスタンスは同様の理由でグレーアウトされていた。

    結論

    とりあえずこのサーバだけはt2.smallで継続することにした

  • Ansibleで特定サーバのみ振る舞いを変える

    Ansibleで特定サーバのみ振る舞いを変える

    はじめに

    Ansibleで構成管理をしていると、ある全台ではなくある特定のサーバだけ設定を変えたい時がある。WEBサーバ全体ではなく、その中の1台のみ実施したいという時だ。Ansibleのtipsを紹介。

    Ansibleで特定サーバのみ振る舞いを変える

    例えばcronをコメントアウトすることを例にする。

    1. Ansibleのinventoriesのhostsファイルを変更
      以下の通り
      [web]
      web01 ansible_host=192.168.10.10 ansible_ssh_private_key_file=~/.ssh/key
      web02 ansible_host=192.168.10.11 ansible_ssh_private_key_file=~/.ssh/key
      
      
    2. ansible-playlistを編集し、サーバ名を取得する。
      - name: check Hostname
      hostname:
      name: '{{ inventory_hostname }}'
      tags: edit_cron

      inventoriesでいうところのweb01やweb02がこれに該当する

    3. replaceを使う
      特定サーバの場合振る舞いを変える
      web02だけあるcronを変えたい場合
      - name: comment out for 02
      replace:
      path: /var/spool/cron/cronuser
      regexp: '^\* \* \* \* \* echo hello'
      replace: '#* * * * * echo hello'
      when: inventory_hostname is search("02")
      tags: edit_cron

      を定義することにより、02のecho helloだけコメントアウトされる

     

  • redash v5 (5.0) でIn Progress状態のゾンビプロセスをkillする

    redash v5 (5.0) でIn Progress状態のゾンビプロセスをkillする

    はじめに

    重いクエリを投げるとredashが固まってしまい、他のプロセスが起動できなくなることがある。キューを削除する方法を紹介

    前提

    Docker版redash v5 (5.0)

    手順

    dockerにログイン

    docker exec -it redash_redis_1 /bin/sh  

    redisを操作する

    /data # redis-cli 
    127.0.0.1:6379> type query_task_trackers:in_progress
    zset
    127.0.0.1:6379> zrange query_task_trackers:in_progress 0 -1
    1) "query_task_tracker:8b39ba38-7ce9-49f1-8c57-2633ac4bc05b"
    127.0.0.1:6379> zrem query_task_trackers:in_progress query_task_tracker:8b39ba38-7ce9-49f1-8c57-2633ac4bc05b
    (integer) 1
    127.0.0.1:6379> zrange query_task_trackers:in_progress 0 -1
    (empty list or set)
    127.0.0.1:6379> 

    結果

    無事キューが削除された

    最後に

    おそらくversion 5以前でも同様の手順で削除可能

  • 自分のグローバルIPアドレスを確認する

    自分のグローバルIPアドレスを確認する

    はじめに

    内部通信で使うサーバやMacBookのアドレスはOSに付いているip addrなどのコマンドでわかるけど、残念ながらグローバルIPはOSを確認するコマンドはOSに付いていない。外部サービスに問い合わせ、自分のIPを聞くのが一般的だ

    自分のIPを確認する

    ブラウザで確認する場合

    https://www.cman.jp/network/support/go_access.cgi へアクセスすると、接続しているマシンのIPアドレスがわかる。他の拠点のスタッフにIPアドレスを確認するのによく使う。

    CUIで確認する場合

    $ curl -s httpbin.org/ip 
    {
      "origin": "123.12.1.11"
    }
    

    IPのみを取り出したい場合はjqコマンドを使うとIPのみ取り出せる。

    $ curl -s httpbin.org/ip | jq -r .origin
    123.12.1.11

     

  • mysqlのユーザー作成や権限付与の際よく使うコマンド

    mysqlのユーザー作成や権限付与の際よく使うコマンド

    はじめに

    mysqlのユーザーを作成し、権限を適宜設定することが多いがよく使うコマンドをまとめた。

    ユーザーを確認する

    MariaDB [(none)]> select user,host from mysql.user;
    +--------+------------+
    | user   | host       |
    +--------+------------+
    | root   | 127.0.0.1  |
    | redash | 172.18.0.% |
    | root   | ::1        |
    |        | instance-1 |
    | root   | instance-1 |
    |        | localhost  |
    | root   | localhost  |
    +--------+------------+
    7 rows in set (0.00 sec)
    
    MariaDB [(none)]> show grants for 'redash'@'172.18.0.%';

    ユーザーを作成する

    パスワードを平文で入れる

    GRANT USAGE ON *.* TO 'usera'@'192.168.10.10' IDENTIFIED BY 'password123';

    パスワードをハッシュ化して登録

    SELECT PASSWORD('password123');
    #出力されたハッシュを以下に入れる
    GRANT USAGE ON *.* TO 'usera'@'192.168.10.10' IDENTIFIED BY PASSWORD '*6D4B1BD281FE14CCBC97B934';

    権限付与

    参照のみ

    GRANT SELECT, EXECUTE ON `databasea`.* TO 'usera'@'192.168.10.10'

    データ操作言語(DML)文

    GRANT SELECT, INSERT, UPDATE, DELETE, EXECUTE ON `databasea`.* TO 'usera'@'192.168.10.10'

    権限を剥奪する

    全権限を剥奪する

    REVOKE ALL ON `database`.* FROM 'usera'@'%' 

    部分的に剥奪する

    REVOKE DROP, REFERENCES, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EVENT, TRIGGER ON `database`.* FROM 'usera'@'%' 

    接続元を変更する

    どこからでもアクセスできた状態からアクセス元を特定セグメントからのみに絞る

    rename user 'usea'@'%' to 'usera'@'192.1.%';

     

    パスワード変更

    すでに作ったユーザのパスワード変更は以下のコマンド

    set password for 'usera'@'192.168.10.10'=password('パスワード');

    ユーザを削除する

    DROP USER 'usera'@'192.168.10.10';

     

  • 無料でhttps化!Let’s Encrypt導入手順

    無料でhttps化!Let’s Encrypt導入手順

    はじめに

    SSL証明書を無料で発行できる時代になった。Let’s enscryptを使えば証明書の有効期間が3ヶ月と極端に短いが、cronなどに仕込めば半永久的に利用が可能。
    今回は、nginxへの組み込みも含めて紹介。

    導入手順

    1 つのドメインへの証明書登録の場合は非常にシンプル。

    ./certbot-auto certonly --no-self-upgrade -n --webroot --agree-tos --email tsukada@test.jp -w /usr/share/nginx/html/sumito -d sumito.jp

    これだけで登録できる。

    Let’s Encryptの証明書は、

    -dオプションでドメインを。

    -w オプションでディレクトリを指定することで複数ドメインを1証明書にまとめることができる。

    以下3つのドメインを1証明書にまとめるとすると、

    • tsukada.test.jp
    • sumito.test.jp
    • coco.test.jp
      手順はこちら。
    cd /usr/local/src
    git clone https://github.com/certbot/certbot
    cd certbot/
    ./certbot-auto certonly --no-self-upgrade -n --webroot --agree-tos --email webadmin@test.jp -w /var/www/tsukada -w /var/www/sumito -d tsukada.test.jp -d sumito.test.jp -d coco.test.jp --expand

    オプションについて

    -w

    -w の部分はドキュメントルートを設定する。ドメイン毎に異なる場合は、-wを増やす。 -w /var/www/tsukada -w /var/www/sumito

    -d

    -dは1枚の証明書に持たせるドメインを入れる場合は、同様に-dを増やす。 -d tsukada.test.jp -d sumito.test.jp

    鍵の作成場所

    以下のディレクトリに作成される

    • /etc/letsencrypt/live/[URL]/fullchain.pem
    • /etc/letsencrypt/live/[URL]/privkey.pem

    nginxの設定

    上記記載の通り、`/etc/letsencrypt/live/`に作成される。

    nginx のserverの設定箇所のところに、以下のように設定することでnginxで証明書を有効化することができる。

    server {
      listen 443 ssl;
      ssl_certificate     /etc/letsencrypt/live/sumito.jp/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/sumito.jp/privkey.pem;
    

    証明書の自動更新

    cronを設定することで証明書の自動更新が可能。 証明書更新後、webサーバを再起動する必要がある。

    0 10 1 * * /usr/local/src/certbot/certbot-auto renew --force-renew && systemctl restart nginx

    参考情報

    15秒でわかるLet’s Encryptのしくみ~無料で複数ドメイン有効な証明書作成~ https://qiita.com/S-T/items/7ede1ccfae6fc7f08393

  • JavaScriptの制御をGoogle Chrome側で変更する

    JavaScriptの制御をGoogle Chrome側で変更する

    はじめに

    redash v5のベータ版がお披露目された。注目の機能の一つ、AleartがChatWorkにも飛ばせるようになった。

    しかし

    バグなのだろうか、登録しようとしても有効化しようとしてもSaveが押せない。JavaScript側で制御していて、その制御がおかしいようだ。ベータ版なのでこのような問題もあるのだろう

    対処

    pull requestを投げて解決した。

    と言えればかっこいいが、今回はクライアント(私のブラウザ)のJavaScriptをいじり、無理やり制御を解除した

    JavaScriptの制御をユーザー側で変更する

    Chromeのdeveloper modeを使う

    Macであれば[ Option + command + i ]

    手順は以下の通り

    その後、Saveボタンは無事押すことができるようになり、保存も成功した

    設定したアラートも文字登録された。

     

  • GitLabでCIを動かす

    GitLabでCIを動かす

    はじめに

    Gitと連携させCIを動かす事が増えて来た。GitHubと連携させTravis CIもいいし、Circle CIもいいだろう。GitLab CIのいいところは、自前で環境さえ準備できさえすれば非常に低コスト(IaaSの運用コストのみ)で動かす事ができるのが魅力だ

    設定方法

    前提条件

    GitLabが動作できていること

    本手順は、GitLab 10.3.7を前提としている

    GitLabの設定

    GitLabの左メニューで Settings -> CI/CDを選択する

    General piplines settingsで
    「Instance default」を選択し、Runner Tokenをひかえる

     

    CIサーバ側の設定

    GitLab CIサーバでGitLab runnerの設定をいれる

    # sudo gitlab-runner register
    
    Running in system-mode.
    
    Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
    https://git.sumito.jp/
    Please enter the gitlab-ci token for this runner:
    SJxxxxxxxxxxxx
    Please enter the gitlab-ci description for this runner:
    [server]: projectA
    Please enter the gitlab-ci tags for this runner (comma separated):
    
    Whether to lock the Runner to current project [true/false]:
    [true]: true
    Registering runner... succeeded runner=xxxxxxx
    Please enter the executor: parallels, ssh, virtualbox, docker+machine, docker-ssh+machine, docker, docker-ssh, shell, kubernetes:
    shell
    Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

     

    automatically reloaded!と表示されれば設定は問題ない

    .gitlab-ci.ymlの設定

    .gitlab-ci.yml 
    variables:
    AMI_ID_KUSANAGI: "ami-123456"
    INSTANCE_NAME: "${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}_${CI_PIPELINE_ID}"
    TMP_ANSIBLE_DIR: "/tmp/gitlab/${CI_COMMIT_SHA}/${CI_PIPELINE_ID}"
    DEPLOY_SITE: "wordpress"
    SSH_KEY_NAME: "ssh-key"
    INSTANCE_LOG: "/tmp/gitlab/${CI_COMMIT_SHA}/${CI_PIPELINE_ID}/run-instances.json"
    ALLOCATE_ADDRESS_LOG: "/tmp/gitlab/${CI_COMMIT_SHA}/${CI_PIPELINE_ID}/allocate-address.json"
    
    stages:
    - initial
    - build
    - test
    - deploy
    - cleanup
    
    ########################################################
    # STAGE:ANCHORS #
    ########################################################
    
    # These are hidden jobs that contain the anchors
    .server_group_1:
    only: &kusanagi_servers
    /^(wordpress).*$/
    
    ########################################################
    # STAGE:INITIAL #
    ########################################################
    make_tmp_directory:
    stage: initial
    script:
    - mkdir -p ${TMP_ANSIBLE_DIR}
    
    # デプロイテスト用インスタンスを作成@kusanagi& t2.medium
    run_aws_server_kusanagi:
    stage: build
    only:
    - *kusanagi_servers
    script:
    # インスタンスを作成
    - aws ec2 run-instances --key-name ${SSH_KEY_NAME} --image-id ${AMI_ID_KUSANAGI} --count 1 --instance-type t2.medium --security-group-ids sg-12345678 --subnet-id subnet-12345678> ${INSTANCE_LOG}
    # インスタンスにタグ付け
    - instance_id=`jq -r .Instances[].InstanceId ${INSTANCE_LOG}`
    - aws ec2 create-tags --resources $instance_id --tags Key=Name,Value=${INSTANCE_NAME}
    # ElasticIPを払い出してタグ付け
    - aws ec2 allocate-address --domain vpc > ${ALLOCATE_ADDRESS_LOG}
    - allocation_id=`jq -r .AllocationId ${ALLOCATE_ADDRESS_LOG}`
    - aws ec2 create-tags --resources $allocation_id --tags Key=Name,Value=${INSTANCE_NAME}
    # インスタンスのステータスがrunningになるまで待つ
    - i=1; while [ $i -le 120 ]; do sleep 3; if [ "\"running\"" = `aws ec2 describe-instances --instance ${instance_id} | jq '.Reservations[].Instances[].State.Name'` ]; then break; fi; i=$(expr $i + 1); done
    # ElasticIPをインスタンスに紐付け
    - aws ec2 associate-address --allocation-id ${allocation_id} --instance ${instance_id}
    
    # Ansible Playbookをgit clone
    setup_git:
    stage: build
    script:
    - cd ${TMP_ANSIBLE_DIR}
    - git clone git@git.sumito.jp:sumito/projectA.git
    - cd projectA
    - git checkout ${CI_COMMIT_REF_NAME}
    
    ########################################################
    # STAGE:TEST #
    ########################################################
    # 全てのAnsible Playbookの構文チェック
    check_syntax_ansible:
    stage: test
    before_script:
    - sed -e s%__ROLES_PATH__%${TMP_ANSIBLE_DIR}/projectA/Ansible/roles%g /home/gitlab-runner/.ansible_template.cfg > /home/gitlab-runner/.ansible.cfg
    - cd ${TMP_ANSIBLE_DIR}/projectA/Ansible
    script:
    - ansible-playbook -i inventories/staging/hosts sites/staging/wordpress.yml --syntax-check
    
    ########################################################
    # STAGE:DEPLOY #
    ########################################################
    # Ansible Deployを実行
    execute_ansible:
    stage: deploy
    only:
    - *kusanagi_servers
    before_script:
    - elastic_ip=`jq -r .PublicIp ${ALLOCATE_ADDRESS_LOG}`
    - cd ${TMP_ANSIBLE_DIR}/projectA/Ansible
    - sed -si -e s/__IP__/${elastic_ip}/g -e s%__KEY__%/home/gitlab-runner/.ssh/${SSH_KEY_NAME}%g inventories/staging/hosts
    - sleep 20
    script:
    - ansible-playbook -i inventories/staging/hosts sites/staging/${DEPLOY_SITE}.yml -u sumito --limit ${DEPLOY_SITE} --vault-password-file=/home/gitlab-runner/.vault_password
    
    ########################################################
    # STAGE:CLEANUP #
    ########################################################
    # Deployテストしたサーバリソースを削除@on_success
    remove_aws_server_success:
    stage: cleanup
    only:
    - *kusanagi_servers
    before_script:
    - instance_id=`jq -r .Instances[].InstanceId ${INSTANCE_LOG}`
    script:
    - sh /home/gitlab-runner/bin/aws/terminate-instance.sh $instance_id
    after_script:
    - rm -rf ${TMP_ANSIBLE_DIR}
    
    # Deployテストしたサーバリソースを削除@on_failure
    remove_aws_server_manual:
    stage: cleanup
    only:
    - *kusanagi_servers
    before_script:
    - instance_id=`jq -r .Instances[].InstanceId ${INSTANCE_LOG}`
    script:
    - sh /home/gitlab-runner/bin/aws/terminate-instance.sh $instance_id
    after_script:
    - rm -rf ${TMP_ANSIBLE_DIR}
    when: manual

    どのように動くか

    今回実施したPipelineは

    “`
    stages:
    – initial
    – build
    – test
    – deploy
    – cleanup
    “`

    の順番で処理が行われるよう骨格を作った。

    AWSのインスタンスをたちあげ、環境をセットアップし、ansibleを流す。最後にはテストで使ったイメージを削除するPipeline
    gitにpushする度にCIが動くようになり、品質が担保できるようになった。