20191218のdockerに関する記事は21件です。

Docker コンテナ内から他の Docker コンテナにdocker exec実行

AWS Workspaces
Amazon Linux2
nginx 1.16.1

Dockerfile
FROM alpine:3.10.3

RUN apk --update add logrotate docker && rm -rf /var/cache/apk/*

# cronのdaemonを起動(log level=1, foreground)
CMD crond -l 1 -f
# Dockerイメージ作成
docker build -t base .

# Dockerコンテナ起動
docker run -d \
--name nginx \
-p 81:80 \
nginx:1.16.1

docker run -d \
--name base \
-v /var/run/docker.sock:/var/run/docker.sock \
base:latest

$ docker exec -it base sh
/ # docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
34f2c8645bd2        base:latest         "/bin/sh -c 'crond -…"   18 seconds ago      Up 17 seconds                            base
e48c7a09b233        nginx:1.16.1        "nginx -g 'daemon of…"   42 minutes ago      Up 42 minutes       0.0.0.0:81->80/tcp   nginx

/ # docker exec -it nginx bash
root@e48c7a09b233:/# ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr

root@e48c7a09b233:/# nginx -v
nginx version: nginx/1.16.1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

誕生日にCTFを開催してみた

TL;DR

  • CTF開催にCTFdは非常に便利
  • GCE上でDockerコンテナを11個立てていたが, 問題なく動いた
  • 開催日程は事前に発表しておくと良い

もくじ

はじめに

2019年12月5日 0:00から2019年12月6日 0:00までの1日間にtaskCTFを開催しました。

taskCTFの開催にあたり行った環境構築や, CTF自体の振り返りを共有するために本記事を投稿します。

なお, taskCTFで使用した問題はGitHubで公開しています。
Dockerfile等のconfigファイル群, 問題およびwriteupが入れてあるので, もしよければご参照ください。
task4233/taskctf19

ご参加いただいた方々はありがとうございました!
おかげさまで非常に思い出に残る誕生日になりました。

score

CTFの環境構築

GCEのインスタンス上でDockerコンテナを11個立てました。

内訳は以下の通りです。

  • Web問サーバ2個
  • Pwn問サーバ5個
  • コンテストサーバ4個

containers

GCEをしっかり使ったことが無かったため, 以下の記事を参考にして環境構築を行いました。特に困ったことはありませんでした。

これから始めるGCP(GCE) 安全に無料枠を使い倒せ

これらのコンテナは大きく分けて以下の3つのグループに分けられます。

コンテストサーバ(CTFd)

以下の4つのコンテナはスコアサーバである, CTFdのために使用しました。

  • CTFd
  • DB
  • Radius
  • Postfix

CTFdはdocumentationがあり非常に楽でした。
install方法は, flaskで手動立ち上げかdocker runかdocker-compose upが使用できたため, docker-compose upで楽をしました。

結果的に問題なく動いてくれたので良かったです。

先日参加したCTFのイベントでも運営の方にお聞きしたところ, 「CTFdを使い初めてからコンテストサーバが落ちたことがない」とのことだったので, CTFdは運用面で信頼性が高そうです(内部でradiusが動いているので分散もうまいことやってくれます)

Web問サーバ

以下の2つのコンテナはWeb問サーバに使用しました。

  • Bad Frontend-1
  • Bad-Frontend-2

php-fpmの公式イメージを使っていました。

以下, Dockerfileです。

// Dockerfile
FROM php:7.2-fpm
COPY php.ini /usr/local/etc/php

CMD ["php", "-S", "0.0.0.0:80"]

今回のWeb問はサーバにリクエストを送りつける問題だったので, 負荷が心配でしたが全く問題なかったようでした。
ちなみに, ローカルのPCからDOSもどきをしたのですが, GCEのCPU負荷を見ると殆ど影響がない様子でした。
アプリケーション自体の負荷が少なかっただけかもしれませんが, 結果的に何も起きなかったので良かったです。

Pwn問サーバ

以下の5つのコンテナをPwn問サーバに使用しました。

  • peep
  • loop
  • leetspeak
  • 334
  • peep2

このうち, 上の3つは予想通りの実行が為されなかったため, 実際にポートを公開したのは下の2つのみでした。

今思うと, tcpで公開していたので, udpで公開した方がよかったのかもしれません。

Hostingにはmegumishさんに助言をいただき, xinetdを利用しました。

https://twitter.com/megumish/status/1202466987332341760?s=20

1時間くらいで構築したので, 変な部分があるかもしれません。

以下, Dockerfileです。

// Dockerfile
FROM ubuntu:16.04
RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.ustc.edu.cn/g" /etc/apt/sources.list
RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y lib32z1 xinetd
CMD ["/usr/sbin/xinetd", "-dontfork"]

このxinetdでホスティングしたものの, buffer overflowを狙った問題は正しくnetcatで繋がりませんでした。
format string attackを使う問題では問題なかったんですが......
原因がよくわからないので, xinetdについてわかる方がいれば助言をください。

CTFの運用

以下がCPUの使用率です。

cpu

ホスティング時の3日とコンテスト終了間際にかなり高くなっています。

network

ネットワークのパケットトラフィックを見ると, 同じような時間帯に受信パケが多くなっています。そのため, Pwn問追加直後の影響だと考えられます。

絶対にサーバを落としてはならないと考えていたため, トラフィックが集中すると考えていた当初は, Pwn問を公開せずにコンテナ数を5つにしていました。

想定していたよりも負荷が少ない様子だったので, もっとコンテナを増やしても良かったのかもしれません。
何はともあれ, 結果的に安定したホスティングができ, 良かったと考えています。

開催したCTFの振り返り

開催後にアンケートを取り, 20人から回答が得られました。
全体の95%もの方が「楽しめた」と回答してくださり, 非常に嬉しかったです。

良かった点として頂いたご意見は, 大きく分けて以下の4点でした。

  • 理不尽なエスパー問題が無かった点
  • Pwn問があった点
  • 難しすぎず初心者でも楽しめた点
  • サーバが安定していた点

悪かった点として頂いたご意見は, 大きく分けて以下の6点でした。

  • 問題追加が遅かった点
  • 問題ジャンルが少なかった点
  • 同じ解法で解ける問題があった点
  • 開催するタイミングが事前公開されなかった点
  • 開催時間が分かりにくかった点
  • Miscが多すぎた点

問題追加が遅かった点
問題ジャンルが少なかった点

申し訳なかったです。しかし, できる全力を尽くしたので, 温かい目で見ていただきたいです。

同じ解法で解ける問題があった点

Pwn問とWeb問についてです。

Pwn問では, FSAで解ける問題でFlagが同じ場所にあったため同じコードでFlagが取れてしまったようです。
常識なのかもしれませんが, Flagの宣言場所は変えた方が良いようでした。

Web問では, 同様にローカルプロキシを使っている方は同じ解法で溶けてしまったようです。もう少しひねった方が良かったかもしれないです。

Miscが多すぎた点

Miscが多すぎたというか, 他ジャンルが少なかったということだと思います。

おわりに

以上, 環境構築とコンテストの反省でした。
今回製作した問題は, 理不尽問題をなくすつもりで作っていたので, 感想で「理不尽問がなかった」という感想をいただけて非常に嬉しかったです。

感想等はTwitterのハッシュタグ#taskctfをご覧ください。

また, 環境構築や運用において不明点等があれば, 答えられる範囲で答えたいので, お気軽にお聞きください!

ここまでお読みいただきありがとうございました!
あなたも誕生日にCTFを開催してみてはいかがでしょうか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

誕生日に開催したCTFの環境構築と振り返り

TL;DR

  • CTF開催にCTFdは非常に便利
  • GCE上でDockerコンテナを11個立てていたが, 問題なく動いた
  • 開催日程は事前に発表しておくと良い

もくじ

はじめに

誕生日なのでCTFを開催するか, と言う気持ちで,
2019年12月5日 0:00から2019年12月6日 0:00までの1日間にtaskCTFを開催しました。

taskCTFの開催にあたり行った環境構築や, CTF自体の振り返りを共有するために本記事を投稿します。

なお, taskCTFで使用した問題はGitHubで公開しています。
Dockerfile等のconfigファイル群, 問題およびwriteupが入れてあるので, もしよければご参照ください。
task4233/taskctf19

ご参加いただいた方々はありがとうございました!
おかげさまで思い出に残る誕生日になりました。

score

CTFの環境構築

GCEのインスタンス上でDockerコンテナを11個立てました。

内訳は以下の通りです。

  • Web問サーバ2個
  • Pwn問サーバ5個
  • コンテストサーバ4個

containers

GCEをしっかり使ったことが無かったため, 以下の記事を参考にして環境構築を行いました。特に困ったことはありませんでした。

これから始めるGCP(GCE) 安全に無料枠を使い倒せ

これらのコンテナは大きく分けて以下の3つのグループに分けられます。

コンテストサーバ(CTFd)

以下の4つのコンテナはスコアサーバである, CTFdのために使用しました。

  • CTFd
  • DB
  • Radius
  • Postfix

CTFdはdocumentationがあり非常に楽でした。
install方法は, flaskで手動立ち上げかdocker runかdocker-compose upが使用できたため, docker-compose upで楽をしました。

結果的に問題なく動いてくれたので良かったです。

先日参加したCTFのイベントでも運営の方にお聞きしたところ, 「CTFdを使い初めてからコンテストサーバが落ちたことがない」とのことだったので, CTFdは運用面で信頼性が高そうです(内部でradiusが動いているので分散もうまいことやってくれます)

Web問サーバ

以下の2つのコンテナはWeb問サーバに使用しました。

  • Bad Frontend-1
  • Bad-Frontend-2

php-fpmの公式イメージを使っていました。

以下, Dockerfileです。

// Dockerfile
FROM php:7.2-fpm
COPY php.ini /usr/local/etc/php

CMD ["php", "-S", "0.0.0.0:80"]

今回のWeb問はサーバにリクエストを送りつける問題だったので, 負荷が心配でしたが全く問題なかったようでした。
ちなみに, ローカルのPCからDOSもどきをしたのですが, GCEのCPU負荷を見ると殆ど影響がない様子でした。
アプリケーション自体の負荷が少なかっただけかもしれませんが, 結果的に何も起きなかったので良かったです。

Pwn問サーバ

以下の5つのコンテナをPwn問サーバに使用しました。

  • peep
  • loop
  • leetspeak
  • 334
  • peep2

このうち, 上の3つは予想通りの実行が為されなかったため, 実際にポートを公開したのは下の2つのみでした。

今思うと, tcpで公開していたので, udpで公開した方がよかったのかもしれません。

Hostingにはmegumishさんに助言をいただき, xinetdを利用しました。

https://twitter.com/megumish/status/1202466987332341760?s=20

1時間くらいで構築したので, 変な部分があるかもしれません。

以下, Dockerfileです。

// Dockerfile
FROM ubuntu:16.04
RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.ustc.edu.cn/g" /etc/apt/sources.list
RUN apt-get update && apt-get -y upgrade
RUN apt-get install -y lib32z1 xinetd
CMD ["/usr/sbin/xinetd", "-dontfork"]

このxinetdでホスティングしたものの, buffer overflowを狙った問題は正しくnetcatで繋がりませんでした。
format string attackを使う問題では問題なかったんですが......
原因がよくわからないので, xinetdについてわかる方がいれば助言をください。

CTFの運用

以下がCPUの使用率です。

cpu

ホスティング時の3日とコンテスト終了間際にかなり高くなっています。

network

ネットワークのパケットトラフィックを見ると, 同じような時間帯に受信パケが多くなっています。そのため, Pwn問追加直後の影響だと考えられます。

絶対にサーバを落としてはならないと考えていたため, トラフィックが集中すると考えていた当初は, Pwn問を公開せずにコンテナ数を5つにしていました。

想定していたよりも負荷が少ない様子だったので, もっとコンテナを増やしても良かったのかもしれません。
何はともあれ, 結果的に安定したホスティングができ, 良かったと考えています。

開催したCTFの振り返り

開催後にアンケートを取り, 20人から回答が得られました。
全体の95%もの方が「楽しめた」と回答してくださり, 非常に嬉しかったです。

良かった点として頂いたご意見は, 大きく分けて以下の4点でした。

  • 理不尽なエスパー問題が無かった点
  • Pwn問があった点
  • 難しすぎず初心者でも楽しめた点
  • サーバが安定していた点

悪かった点として頂いたご意見は, 大きく分けて以下の6点でした。

  • 問題追加が遅かった点
  • 問題ジャンルが少なかった点
  • 同じ解法で解ける問題があった点
  • 開催するタイミングが事前公開されなかった点
  • 開催時間が分かりにくかった点
  • Miscが多すぎた点

問題追加が遅かった点
問題ジャンルが少なかった点

申し訳なかったです。しかし, できる全力を尽くしたので, 温かい目で見ていただきたいです。

同じ解法で解ける問題があった点

Pwn問とWeb問についてです。

Pwn問では, FSAで解ける問題でFlagが同じ場所にあったため同じコードでFlagが取れてしまったようです。
常識なのかもしれませんが, Flagの宣言場所は変えた方が良いようでした。

Web問では, 同様にローカルプロキシを使っている方は同じ解法で溶けてしまったようです。もう少しひねった方が良かったかもしれないです。

Miscが多すぎた点

Miscが多すぎたというか, 他ジャンルが少なかったということだと思います。

おわりに

以上, 環境構築とコンテストの反省でした。
今回製作した問題は, 理不尽問題をなくすつもりで作っていたので, 感想で「理不尽問がなかった」という感想をいただけて非常に嬉しかったです。

感想等はTwitterのハッシュタグ#taskctfをご覧ください。

また, 環境構築や運用において不明点等があれば, 答えられる範囲で答えたいので, お気軽にお聞きください!

ここまでお読みいただきありがとうございました!
あなたも誕生日にCTFを開催してみてはいかがでしょうか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jib-maven-pluginを使ってSpringBootアプリをイメージ化してDockerで起動

はじめに

この記事は、Java Advent Calendar 2019 - Qiita 23日目の記事です。

皆さん、コンテナ使ってますか??
Googleが公開しているjib-maven-pluginを使用すれば、Javaで作成されたアプリを簡単にDockerイメージにすることができます。
今回はSpringBoot・Mavenを使用したSpringBootアプリケーションのイメージビルド及びコンテナ立ち上げまでやってみます(本当はAWSデプロイまでやりたかったけど時間がないので断念)

環境

  • Java8
  • SpringBoot2.1.9.RELEASE
  • Maven3.6.3
  • jib-maven-plugin1.8.0
  • Docker19.03.5

SpringBootアプリケーションを構築する

SpringBoot・Mavenで構築されたプロジェクトを作成します。
Springから提供されているSpring Initializrを使用すれば簡単にアプリケーションを構築することができます。

使用方法はこちら(後日使用方法をアップします)

サンプルControllerの追加

Webアプリケーションを今回は作成するので、サンプルのController及びhtmlを追加します。

IndexController

@Controller
public class IndexController {

    @GetMapping("/")
    public ModelAndView get(ModelAndView mav) {
        mav.setViewName("index");
        return mav;
    }
}

index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>JibSampleApp</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet"
    href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
    crossorigin="anonymous">

<!-- Optional theme -->
<link rel="stylesheet"
    href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
    integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp"
    crossorigin="anonymous">

<!-- Latest compiled and minified JavaScript -->
<script
    src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
    integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
    crossorigin="anonymous"></script>

</head>
<body>
    <div class="container">
        <div class="row">
            <div class="jumbotron">
                <h1>JibSampleApp</h1>
                <p>this app is JibSampleApp</p>
            </div>
            <div class="panel panel-success">
                <div class="panel-heading">blank</div>
                <div class="panel-body"></div>
            </div>
        </div>
    </div>
</body>
</html>

プラグインを設定

上記で作成したSpringBootアプリケーションのpomに以下のプラグインを追加します。

pom.xml

<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <version>1.8.0</version>
    <configuration>
        <to>
            <image>jibsampleimage</image>
        </to>
    </configuration>
</plugin>

build実行

Dockerが起動している状態で、
mvn compile jib:dockerBuild
を実行することで、SpringBootアプリケーションのイメージbuildが実行されます。
Windowsなどで管理者権限を剥奪されている場合、うまく動作しないことがあるので、その場合は一時的に管理者権限を付与する必要があります。

buildがうまくいくと、Dockerのイメージ一覧の中に作成したイメージがあることが確認できます。
docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
jibsampleimage      latest              bcb3c6749044        49 years ago        161MB

Dockerコンテナ起動

上記でbuildしたイメージをもとにDockerコンテナを作成し、実行します。
以下のコマンドを入力することで、ローカルの8080ポートとDockerの8080をマッピングし、ブラウザからアクセスし確認することができます。
docker run -p 8080:8080 -it jibsampleimage

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.9.RELEASE)

2019/12/18 14:11:53.177 [main] INFO   c.a.j.s.JibSampleApp Starting JibSampleApp on be8d974734e4 with PID 1 (/app/classes started by root in /)
2019/12/18 14:11:53.189 [main] INFO   c.a.j.s.JibSampleApp No active profile set, falling back to default profiles: default
2019/12/18 14:11:56.123 [main] WARN   o.m.s.m.ClassPathMapperScanner No MyBatis mapper was found in '[com.atu496.jib.sample]' package. Please check your configuration.
2019/12/18 14:11:58.065 [main] INFO   o.s.b.w.e.t.TomcatWebServer Tomcat initialized with port(s): 8080 (http)
2019/12/18 14:11:58.166 [main] INFO   o.a.c.h.Http11NioProtocol Initializing ProtocolHandler ["http-nio-8080"]
2019/12/18 14:11:58.213 [main] INFO   o.a.c.c.StandardService Starting service [Tomcat]
2019/12/18 14:11:58.217 [main] INFO   o.a.c.c.StandardEngine Starting Servlet engine: [Apache Tomcat/9.0.26]
2019/12/18 14:11:58.535 [main] INFO   o.a.c.c.C.[.[.[/] Initializing Spring embedded WebApplicationContext
2019/12/18 14:11:58.537 [main] INFO   o.s.w.c.ContextLoader Root WebApplicationContext: initialization completed in 5194 ms
2019/12/18 14:11:59.277 [main] INFO   o.s.s.c.ThreadPoolTaskExecutor Initializing ExecutorService 'applicationTaskExecutor'
2019/12/18 14:11:59.532 [main] INFO   o.s.b.a.w.s.WelcomePageHandlerMapping Adding welcome page template: index
2019/12/18 14:12:00.865 [main] INFO   o.a.c.h.Http11NioProtocol Starting ProtocolHandler ["http-nio-8080"]
2019/12/18 14:12:01.033 [main] INFO   o.s.b.w.e.t.TomcatWebServer Tomcat started on port(s): 8080 (http) with context path ''
2019/12/18 14:12:01.050 [main] INFO   c.a.j.s.JibSampleApp Started JibSampleApp in 9.135 seconds (JVM running for 10.809)

実際にlocalhost:8080にアクセスすると。。。

スクリーンショット 2019-12-18 23.14.07.png

起動できていることが確認できると思います。

終わりに

SpringBootアプリケーションの簡単なイメージの作成及びコンテナ起動することができました。
ただ、これはまだローカルでの話なので、時間ができ次第AWS・GCP・Azureのコンテナ実行環境へのデプロイ方法をまとめる予定です(本来はそっちがメインだった。。。)
今回作成したソースコードはGithub上にアップしています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ARMベースのEC2インスタンスを利用する

この記事は、ニフティグループ Advent Calendar 2019 の18日目の記事です。
昨日は @fuku710 さんのハッカソンのハックためになりましたね!
私は今月 AWS re:Invent 2019 に参加してきたのですが、同イベント中にも Hackathons and Jams というハッカソンが開催されていたので、次の機会があれば、@fuku710 さんの記事を参考にしながら参加したいですね!

残念ながら、ハッカソンには参加できませんでしたが、たくさんのセッションやワークショップに参加したので、その中から ARM ベースのEC2インスタンスを利用するワークショップ CMP306 - Getting started with Arm-based Amazon EC2 instances を紹介したいと思います。

はじめに

AWS の ARM への取り組みは 2004年 Amazon Lab126 (Kindle などを作っているチーム) から始まったそうです。その後、ネットワークのオフロードや Nitro システムの構築など、カスタムシリコンの開発に情熱的に取り組んできたようです。
今年は Graviton2 を採用した次世代の ARM インスタンス(M6g/R6g/C6g)も発表されました。

Graviton と比べると性能も7倍に上がったようですし、コストも20%程度は削減できるということで、是非一度使ってみたい!と思いワークショップに参加しました。

ワークショップの内容

1. arm64 で Java を動かす

Amazon Corretto を使用したアプリを A1インスタンスで動作させます。

  • インスタンスの作成(AMI は Amazon Linux 2 を選択し、アーキテクチャに 64-bit Arm を選ぶこと)
  • SSH でログインし Amazon Corretto 8 を amazon-linux-extras でインストールします
  • 同様に amazon-linux-extrastomcat8.5 もインストールし簡単のJSPを用意します
  • セキュリティグループで 8080 を開けて結果が表示されれば成功です!簡単ですね!

2. x86 と arm64 インスタンス混在でコンテナを動かす

  • ELB のバックエンドに x86 と arm64 インスタンスを作成する(t3.small を追加)
  • docker buildx を実行して x86 と arm64 のコンテナを同時に作成する

3.arm64 で CodeBuild を実行し BB を動かす

参考資料(※ワークショップで使ったURLなので1ヶ月程度で消えるかも)
https://arthurpt-public-ui.s3-eu-west-1.amazonaws.com/reInvent2019-cmp306-arm-instances/index.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ECS(Fargate)へssh接続してみた

はじめに

この記事はコンテナ勉強用として試したことまとめたものです。
ECS(Fargate)にsshログインする要件があったので実施したものをまとめました。

Fargateについて

  • Fargateにssh接続はできない。
  • sshdをインストールして接続する必要がある。

テンプレート取得

git clone https://github.com/sawanoboly/amazonlinux-sshd

イメージの作成

  • 以下コマンドを実行してイメージを作成する。
  • Dockerfileにsshdやawscliなどのインストール設定が書かれています。
docker build -t amazonlinux-sshd:latest .

ECRにプッシュ

  • ECRにログインする。
$(aws ecr get-login --no-include-email --region ap-northeast-1)
  • タグ付けをする。
docker tag amazonlinux-sshd:latest 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr-repo:latest
  • プッシュする。
docker push 111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr-repo:latest

ECSデプロイの実施

  • サービスを設定する。
    ECSサービス.png

  • タスクを設定する。
    ECSタスク.png

ssh接続する

  • 以下のような形でログイン確認ができる。
Last failed login: Wed Dec 18 12:44:50 UTC 2019 from kd111111111111.au-net.ne.jp on ssh:notty
There was 1 failed login attempt since the last successful login.
debug1: PAM: reinitializing credentials
debug1: permanently_set_uid: 0/0
Environment:
  USER=root
  LOGNAME=root
  HOME=/root
  PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/aws/bin
  MAIL=/var/mail/root
  SHELL=/bin/bash
  SSH_CLIENT=100.100.100.100 56702 22
  SSH_CONNECTION=100.100.100.100 56702 101.101.101.101 22
  SSH_TTY=/dev/pts/0
  TERM=xterm
  AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/1e23t01a-7a1e-3761-bc27-fa4b8473938373
  AWS_EXECUTION_ENV=AWS_ECS_FARGATE
  AWS_DEFAULT_REGION=ap-northeast-1
  AWS_REGION=ap-northeast-1
-bash-4.2#
  • ロールにS3へのアクセス権限を付与すればS3からファイルをダウンロードすることもできる。
  • 既存設定だとssh切断した際にコンテナが停止されてしまうので必要に応じて設定する必要あり。

まとめ

  • Fargateだとsshできないかと思っていたが、やろうと思えばできる。
  • sshdをインストールする必要があるのに気付けなかった。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

drupalをdocker上で簡単に動かしたメモ

はじめに

drupalをDocker上で動かす記事はいくつかありますが、EC2だったり途中だったりでシンプルなものがなかったのでメモしておきます
参考

手順

drupalとmariadbのイメージをdockerhubから取ってきておく

docker pull drupal
docker pull mariadb

以下のファイルを作成

docker-compose.yaml
version: "2"

services:
  # PHP Web Server
  web:
    # Build the Drupal 7 image
    # https://hub.docker.com/r/_/drupal/
    image: drupal:7
    # Environment variables
    environment:
      # Drupal settings
      DRUPAL_PROFILE: standard
      DRUPAL_SITE_NAME: Drupal
      DRUPAL_USER: admin
      DRUPAL_PASS: admin
      DRUPAL_DBURL: mysql://drupal:drupal@database:3306/drupal
    ports:
      - "8000:80"
    volumes:
      - ./drupal:/app
    links:
      - database:database
    command: php -S 0.0.0.0:80 -t /app
    working_dir: /app
    restart: always

  # MySQL Server
  database:
    image: mariadb:10
    environment:
      MYSQL_USER: drupal
      MYSQL_PASSWORD: drupal
      MYSQL_DATABASE: drupal
      MYSQL_ROOT_PASSWORD: ''
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
    ports:
      - "3306:3306"
    restart: always
    volumes:
      - mysql-db:/var/lib/mysql

volumes:
  mysql-db:

docker-compose up -d

Drupalのソースコードをダウンロード・解凍をして、./drupalディレクトリに配置。
Drupal

この時点のディレクトリ構成
.
├── docker-compose.yaml
└── drupal

docker ps -a
mariaDBのdockerIDをメモしておく

docker exec -it (mariaDBのdockerID) bash

mysql -u root -p

→パスワードは上記yamlのMYSQL_ROOT_PASSWORDなので空文字(そのままエンター)

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| drupal             |
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
4 rows in set (0.001 sec)

http://localhost:8000にアクセス!

各種フォームには以下を入れて進んでいけば設定が終わります。

データベース名: drupal
データベースユーザー名: drupal
データベースパスワード: drupal
詳細設定で
ホスト名: database

おわりに

これを費用¥0でクラウドにデプロイしてサイト運用したいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerFileの変更内容を環境に反映したい

docker-compose up --build

イメージの作り直しから、起動までをやってくれる

参考にさせていただいたサイト

https://www.nyamucoro.com/entry/2018/10/29/232724

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【続】WSL2 + AWS Toolkit for Visual Studio Code + SAM local + DynamoDB local

自分用の備忘録程度にと思ったら、タイトルのせいか結構Viewされていて、見て頂いた方に申し訳なく、ちょっと後ろめたくなったので、少し補足します。
主に参考にした記事へのリンクですが。

元々の記事の趣旨はvscode以外、全てWSL2(Ubuntu)のbash上で完結するlambda + dynamodbのローカル開発環境環境を作ってみたいと思い、試行した結果(元々はハマったことの共有)です。

モチベーションは、PowerShellが好きじゃない(食わず嫌い)MS-DOSなオッサンだけど、cmdよりbashが使いたいからです。

※Windows 10 Insider Preview 20H1が前提のため、自己責任でお願いします。
※最近は殆どありませんが、数か月前まで私の環境では1日数回グリーンバックが出てました。
※Windows Update自体がバグってたりすることもあります。
※辞めたいと思っても次のメジャーアップデートまでクリーンインストール以外で製品版に戻せません。製品版でやりたい人は2020年春まで待ってください。

手順全体

  1. Windows 10 Insider Preview 20H1のインストールとWSL2のインストール
  2. docker desktop community 2.1.7.0(41536) edge
  3. WSL2(Ubuntu)に各種ソフトをインストール
    1. Python 3.7.5
    2. pip 19.3.1
    3. aws-cli 1.16.299
    4. aws-sam-cli 0.37.0
  4. DynamoDB local (docker image : amazon/dynamodb-local)
    1. SAM localから通信させるための設定
  5. Visual Studio Code 1.41.0
    1. AWS Toolkit for Visual Studio Code 1.4.0
    2. Remote - WSL 0.41.5
  6. SAM localでHello World
  7. NoSQL Workbench for Amazon DynamoDB (Preview)を使う
  8. SAM localからDynamoDB localに接続してみる

lambda + dynamodbなPythonコード環境をローカルで動かす。

1. Windows 10 Insider Preview 20H1のインストール

そもそも既知の不具合ありの状態です。
この辺を参考に、自分の環境で入れても大丈夫か判断してください。
Tattuの備忘Log

以下の記事など参考にしてインストールしてください。
WSL 2 のインストール手順

※2019/12/17現在、FastリングはDevelopment branchに変わりました。
※WSL2を使うだけならSlowリングからインストールしてください。

「Linux ディストリビューションがインストールされていない場合は~」
のステップでは、Windows StoreからUbuntuをインストールしてから先に進みます。

入れ終わったらお約束の「sudo apt update / sudo apt upgrade」をお忘れなく。

2. docker desktop community 2.1.7.0(41536) edge

公式からダウンロードしてインストールします。
WSL2 で Docker Desktop for Windows (Edge) を利用する

設定が完了すると、ubuntu上でdockerをインストールしなくてもbashからdockerコマンドが使用できます。(便利!)

動作確認にbashから「docker info」してみてください。

3. WSL2(Ubuntu)に各種ソフトをインストール

Pythonかpipのインストールでは先にインストールしておくaptなパッケージが足らず1度やり直しています。
ご注意ください。

3.1. Python / 3.2. pip

pyenvを入れて、そこからインストールしました。
Ubuntu 18.04 に pyenv と Python 3.7.2 をインストールしたメモ

3.3. aws-cli 1.16.299

公式通りpipを使ってインストールしました。
pip自体は既に入っているかもしれないので「pip --version」で確認してみてください。

https://docs.aws.amazon.com/ja_jp/streams/latest/dev/kinesis-tutorial-cli-installation.html

ローカルで使うだけなら「aws configure」のAccess KeyとSecret Access Keyはいい加減で構いません。
そもそもいらないような?
region は「ap-northeast-1」でいいと思います。

3.4. aws-sam-cli 0.37.0

aws-cliと同様にpipを使っていれました。
AWS SAM で Hello World する

この記事と同じようにパッケージが足らず、途中でエラーになった気がします。
必要なパッケージをapt installで追加でいれて再試行したような気がしますが、参考にした記事を失念してしまいました。

4. DynamoDB local (docker image : amazon/dynamodb-local)

普通にdocker runしてイメージを取ってきます。
dockerhub

docker network createして。

bash
$ docker network create aws-local

docker runしたコンテナを停止してから、適当なディレクトリに「docker-compose.yml」を作成して、docker-compose upします。

docker-compose.yml
version: "3"

services:
  dynamodb:
    container_name: dynamodb-local
    image: amazon/dynamodb-local
    volumes:
      - ./data:/data
    ports:
      - 58000:8000
    command: -jar DynamoDBLocal.jar -port 8000 -dbPath /data -sharedDb
    networks:
      - aws-local

networks:
  aws-local:
    external: true

「comannd:」の部分がjavaランタイムに渡す値です。この例ではカレントディレクトリ配下の「./data」に「shared-local-instance.db」を残して他と共有します。このファイルはSQLiteで弄れるらしいのですが、試してません。

bash
user@MYHOSTNAME:~/docker/dynamodb-local$ docker-compose up -d

4.1. SAM localから通信させるための設定

「.bash_profile」(※1)にSAM_DOCKER_NETWORKを環境変数として追加します。

(※1)penvのインストール時に作ったはず。

.bash_profile
export SAM_DOCKER_NETWORK=aws-local
export PYENV_ROOT=$HOME/.pyenv
export PATH=$PYENV_ROOT/bin:$PATH
eval "$(pyenv init -)"
source ~/.bashrc

5. Visual Studio Code 1.41.0

公式通りに普通にインストールします。
Visual Studio Code - コード エディター | Microsoft Azure

5.1. AWS Toolkit for Visual Studio Code 1.4.0 / 5.2. Remote - WSL 0.41.5

vscode上のExtentionから普通にインストールします。
Installing the AWS Toolkit for Visual Studio Code

Remote - WSLはExtentionから入れなくても有効になるようです。(私はExtentionから自前で入れました。)
VSCodeのRemote WSLでWSLを快適に使う

6. SAM localでHello World

ここまで来たらSAM localでPythonなlambda関数を動かせるはずです。

このページの「特徴」→「迅速に使用を開始する方法」にあるイメージのようにコマンドパレットからPython3.7のアプリケーションを作成してください。
https://aws.amazon.com/jp/visualstudiocode/

作成できたら、サイドメニューのファイルのアイコン(explorer)で表示されるツリーから「app.py」を選択してエディタで表示した後にサイドバーの「AWS」のアイコンを選択します。
しばらく待つと「def lambda_handler(event, context):」の上にCodeLensで「Run Locally | Debug~」と出るので「Run Locally」をクリックすればSAM localでアプリケーションが実行されます。

Visual Studio CodeでSAMアプリケーション開発

今回の手順では、全てローカル実行しAWSには接続しないので「Connect to AWS...」は触らなくて良いです。

コンソールにjsonでHello Worldとレスポンスが表示されればOKです。

7. NoSQL Workbench for Amazon DynamoDB (Preview)を使う

最近、「NoSQL Workbench」でDynamoDB localの操作も可能になりましたのでこれを使ってテーブル作成や初期データを投入しました。
次のステップで使用するので適当に簡単なテーブルを作成して、初期データを1件投入しておいてください。

公式からダウンロードしてインストールします。
Download NoSQL Workbench

使い方の参考はこちら。
NoSQL Workbench for Amazon DynamoDB - プレビューの使用が可能になりました

8. SAM localからDynamoDB localに接続してみる

「app.py」を修正してDynamoDB localのコンテナに接続してみます。
テーブル名やPKのカラム名や値は前項で作成した自身のものを使用してください。

app.py
# ~中略~

import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

import boto3

def lambda_handler(event, context):

# ~中略~

    # SAM local上の環境変数を見る
    logger.info('## ENVIRONMENT VARIABLES')
    logger.info(os.environ)
    # 実行時のリクエストを見る
    logger.info('## EVENT')
    logger.info(event)

# ~中略~

    # dockerコンテナなSAM localからだとlocalhostでつながらない。
    # SAM_DOCKER_NETWORKでdynamodb localと同じネットワークでSAM localのコンテナを動くよう指定し、dyanmodb localのコンテナ名と”コンテナ内のポート”番号をendpoint_urlに指定する。
    dynamodb = boto3.resource('dynamodb', endpoint_url="http://dynamodb-local:8000")

    pk = {'YOUR KEY': 'KEY VALUE'}
    table = dynamodb.Table('YOURTABLE NAME')
    res = table.get_item(Key = pk)
    print(res)

# ~中略~

上手く動けば前項で設定した初期データがjson形式で表示されるはずです。

雑感

とりあえずクローズドネットワーク環境でlambda + dynamodbの開発が出来そうな目途が立ちました。
ただ、boto3.resource()のendpoint_url指定はデプロイしたら不要になるようなので、一工夫必要になると思ってます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リアルタイム共同編集ツール『HackMD』をオンプレ運用して、好きな機能を追加して幸せになった話

はじめに

最近、ふと自分の寝顔が気になるので録画して観てみたのですが、
顎が完全に無くなっており完全にピピ美ちゃん状態になっていた @k-waragai です。
痩せようと決意した12月でした。

さて、みなさんは仕事中に作業ログなどを残していますか?

自分は言語化することで見落としが無いことを確認したり他の人へ共有する目的としてメモをしています。

実装する中で分かった事実や背景、バグなどをメモとしてまとめながら進めることで
PR 作成時の Description を作る際にも役に立ち時短にも繋がっています。
ペッって貼り付けるだけで済んだりするので楽

メモツールとして有名なものを上げると

あたりを使っている人が多いのではないでしょうか?

その中でも今回は 『 HackMD 』 に目を付けて
オンプレ運用してみたら好きな機能を追加出来て幸せになった話を書いていこうかなと思います。
(全面的に「HackMD」最高だからみんな使おうぜっていう記事です)

なぜHackMDがいいの?比較してみた

メモツールとして有名なものを比較してみると DocBase と HackMD がいい感じということが分かりました。
※ あくまで個人的な感想を元にまとめています

サービス名 共同編集 画像upload マークダウン メモ単位のロール制限機能 料金 個人的な好み
Evernote × - ベーシック: 無料
プラス: 年額3,100円
×
HackMD 個人利用: 無料
エンタープライズ: 要相談
(人数にもよりますが1人500円程)
Love
Qiita Team × Micro: 月額1,520円
Extra: 月額15,300円
DocBase スターター: 月額900円
プレミアム: 月額19,500円

個人的に ドキュメントとして利用するには、Qiita Team もしくは DocBase がいいとは思うのですが作業メモ程度で使う分にはオーバースペックでした。
重要なドキュメントと混じってしまうのであまり良くありませんでした。

DocBaseであれば Private というロール設定があるので自分のメモ板用に1つドキュメントを作ってという使い方をしていました。
ただ、めんどくさがりの自分は保存ボタンを押すのですら 面倒くさい ので長くは続きませんでした。
(最終的に Atom でメモ取ってました)

そこでいろいろ試した中で良かったのが『 HackMD 』でした。

HackMDはリアルタイム共同編集が可能で保存ボタンとかは特になく 常に保存 されます。
また PDF や Slide などへもエクスポートできます。
自分だけが見れるPrivateや他者の編集を不可能にするLockedなど5種類の設定が行なえます。

HackMD と CodiMD についての概要説明

image.png

HackMD は リアルタイムマークダウン共同編集ツール として 無料 で提供されております。

また HackMD にはオープンソースバージョンがあり CodiMD というものがあります。

? hackmdio/codimd: CodiMD - Realtime collaborative markdown notes on all platforms.

CodiMD では HackMD の大部分の機能を オープンソースとして提供 しており
これらをクローンするだけで簡単に手元でHackMDを動かす事が可能です。

ライセンスも AGPL となっています。

素晴らしいな、おい...。。。

しかも、ここ最近でドキュメントの整備などもしっかりと行われており充実してきています。
? CodiMD Documentation - HackMD

さらにさらに!
Heroku へのワンクリック構築ボタンがあったり、
? https://hackmd.io/c/codimd-documentation/%2Fs%2Fcodimd-heroku-deployment

k8s helm が用意されていたり、
? https://hackmd.io/c/codimd-documentation/%2Fs%2Fcodimd-kubernetes-deployment

Docker Image が用意されていたり、
? https://hackmd.io/c/codimd-documentation/%2Fs%2Fcodimd-docker-deployment

多方面へのカバーも充実しているので CodiMD 様様って感じです٩(๑`ω´๑)۶

まずは clone してみよう

node は Node.js 8.x LTS up to 11.x. となっているようです。
(なので 8以上12以下にしてねって事らしい)

余談ですが、自分が1年ほど前にやった時は node 6系 以上は保証しないよ!と言ってたはずなのに
気づいたらバージョン対応しているので結構メンテされてますね(ㆁωㆁ*) 控えめに言って最高か
ローカル開発用にDockerとかも自分で構築した良い思い出...

MacOS
$ git clone https://github.com/hackmdio/codimd.git
$ cd codimd

環境変数や設定ファイルを眺めて必要な情報をまとめてみた

環境変数についてはこちらを見ると良いです。

本番運用で最低限設定すべき項目は以下の通り

config.json

environment example value description
loglevel info 標準出力に提供されるログの種類を定義します。
domain gyokuro-team.com ドメイン名
useSSL true SSLを利用するか
protocolUseSSL true SSLプロコトルを利用するか
urlAddPort 80 アプリケーションのポート番号
allowAnonymousEdits false 匿名ユーザーの編集を可能にするか
defaultPermission private デフォルトのPermissionの設定
- freely: 誰でも閲覧編集可能
- editable: サインインしていれば閲覧編集可能
- limited: ゲスト以外は閲覧編集可能
- locked: オーナのみ編集可能
- protected: ゲスト以外のオーナのみ
- private: オーナのみしか閲覧編集はできない
allowEmailRegister false Eメールでの登録を可能にするか
email false Eメールでのサインインを許可するか
allowGravatar true アバターを利用するか
sslKeyPath ./cert/privkey.pem SSLを使用する際にprivkeyの場所を指定
sslCertPath ./cert/cert.pem SSLを使用する際にcertの場所を指定
dhParamPath ./cert/fullchain.pem SSLを使用する際にFullChainの場所を指定
sslCAPath ./certchain.pem/ SSLを使用する際にCAの場所を指定
imageuploadtype s3 アップロードの際の画像の置き場
- imgur
- s3
- minio
- azure
- filesystem
github {clientID: "x", clientSecret: "x"} githubでの登録を可能にする場合
s3bucket hackmd-bucket s3でのアップロードを指定した場合
db いろいろ dbの設定情報

環境変数

environment example value description
NODE_ENV production 環境状況
HMD_IMAGE_UPLOAD_TYPE s3 画像のアップロード方法
CMD_DOMAIN production ドメイン名
CMD_URL_ADDPORT production ポート番号
HMD_DB_URL 例: mysql://hackmd:hackmdpass@127.0.0.1:3306/hackmd mysqlやpostgresqlのurlを置く

ローカルで動かしてみよう(Docker)

ドキュメントにもありますが codimd 用の image が配布されています。
image: nabo.codimd.dev/hackmdio/hackmd:1.4.0
? Docker Deployment

ただし今回はそれを使いません。
理由としては、自分が過去にやっていた時は node 6.x系 になっており
当時 11.x系 が最新であったことから、どうしても11.x系にしたく 必要最小限構成で自作 したのでそれで紹介します。

Step1. docker-compose.yml の作成

MacOS
$ touch docker-compose.yml

docker-compose.yml
version: '3'
services:

  # MySQL
  database:
    image: mariadb:10.4.4
    container_name: hackmd_mysql
    environment:
      MYSQL_USER: "${MYSQL_USER:-hackmd}"
      MYSQL_PASSWORD: "${MYSQL_PASSWORD:-hackmdpass}"
      MYSQL_DATABASE: "${MYSQL_DATABASE:-hackmd}"
      MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:-rootpassword}"
    volumes:
      - database:/var/lib/mysql
      - ./provisioning/resources/utf8mb4.cnf:/etc/mysql/conf.d/utf8mb4.cnf
    restart: always
    ports:
      - "${MYSQL_USE_PORT:-3306}:3306"
    networks:
      - app-net

  # Node.js
  app:
    build:
      context: .
      dockerfile: ./provisioning/Dockerfile
    container_name: hackmd_app
    tty: true
    volumes:
      - .:/app
      - /app/node_modules
    working_dir: /app
    env_file:
      - ./provisioning/docker-env
    restart: always
    depends_on:
      - database
    ports:
      - "${APP_USE_PORT:-3000}:3000"
    networks:
      - app-net

networks:
  app-net:
    driver: bridge

volumes:
  database:

Step2. Dcokerfile などを作成

MacOS
$ mkdir -p provisioning/resources
$ touch provisioning/Dockerfile
$ touch provisioning/resources/utf8mb4.cnf
$ touch provisioning/docker-env
$ touch provisioning/resources/docker-entrypoint.sh

  • Dockerfile
provisioning/Dockerfile
FROM node:11.14.0-stretch

ENV DEBCONF_NOWARNINGS=yes

# Set some default config variables
ENV DOCKERIZE_VERSION=v0.6.1

RUN wget https://github.com/jwilder/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz && \
    tar -C /usr/local/bin -xzvf dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz && \
    rm dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz

RUN mkdir /app
WORKDIR /app
COPY . /app

# update
RUN apt-get update

# install
RUN apt-get install -y build-essential vim

RUN yarn install && \
    yarn global add webpack && \
    npm run build

EXPOSE 3000

COPY ./provisioning/resources/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

CMD ["node", "app.js"]

  • utf8mb4.cnf
provisioning/resources/utf8mb4.cnf
# MariaDB-specific config file.
# Read by /etc/mysql/my.cnf

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

[mysqld]
collation-server=utf8mb4_general_ci
init-connect='SET NAMES utf8mb4'
character-set-server=utf8mb4

# Import all .cnf files from configuration directory
!includedir /etc/mysql/mariadb.conf.d/

  • docker-env
.envrc
# dockerで動かすのはdevelopment前提です。

NODE_ENV=development
CMD_CONFIG_FILE=config.json
DEBUG=true
CMD_LOGLEVEL=info
CMD_PROTOCOL_USESSL=false
CMD_URL_ADDPORT=false
CMD_USECDN=true
CMD_ALLOW_ANONYMOUS=false
CMD_ALLOW_ANONYMOUS_EDITS=false
CMD_ALLOW_FREEURL=false
CMD_DEFAULT_PERMISSION=limited
CMD_EMAIL=false
CMD_ALLOW_PDF_EXPORT=true
CMD_ALLOW_EMAIL_REGISTER=false
CMD_ALLOW_GRAVATAR=true
CMD_IMAGE_UPLOAD_TYPE=filesystem

# Change Require
CMD_DB_URL=mysql://hackmd:hackmdpass@database:3306/hackmd
# Githubログイン使いたいなら
# CMD_GITHUB_CLIENTID={YOUR_GITHUB_CLIENT_ID}
# CMD_GITHUB_CLIENTSECRET={YOUR_GITHUB_SECRET_KEY}

# option: Docker Environment
# MYSQL_USER=
# MYSQL_PASSWORD=
# MYSQL_DATABASE=
# MYSQL_ROOT_PASSWORD=
# APP_USE_PORT=

  • docker-entrypoint.sh
provisioning/resources/docker-entrypoint.sh
#!/usr/bin/env bash

dockerize -wait tcp://database:3306 -timeout 30s

node_modules/.bin/sequelize db:migrate

exec "$@"

Step3. configファイル等を整える

MacOS
$ cp config.json.example config.json
$ cp .sequelizerc.example .sequelizerc

  • config.json

一旦、SAML とか 画像S3に上げるととかは一旦考えず最小限で行う。

ログインは Githubログイン で行いたいが後で変更するためここでは行わない。

config.json

{
  "development": {
    "useSSL": false,
    "tmpPath": "./tmp/",
    "defaultNotePath": "./public/default.md",
    "docsPath": "./public/docs",
    "viewPath": "./public/views",
    "uploadsPath": "./public/uploads"
  }
}

Step4. docker up してみよう

MacOS
$ docker-compose up -d

これで localhost:3000 にアクセスすれば見れるので
ローカルでの開発が捗る٩(๑`ω´๑)۶

image.png

※ もし アクセス後 ビルドエラーのような画面が出た場合は以下をお試しください。

$ docker-compose run --rm app npm run build
$ docker-compose run --rm yarn install

Step5. タイトルを変えてみよう

public/views/index/body.ejs の 43行目付近にある以下を修正

- <h1 class="cover-heading"><i class="fa fa-file-text"></i> CodiMD</h1>
+ <h1 class="cover-heading"><i class="fa fa-file-text"></i> k-waragaiMD</h1>

image.png

これでやる気UP (´∀`∩)↑age↑

Step6. Githubログインを出来るようにしてみよう

? https://github.com/settings/developers にアクセスを行い New OAuth App をクリック

必要な情報を入力して Register application をクリック

OAuth_application_settings.png

ClientIDSecretKey が吐き出されるのでそれをメモします。

OAuth_application_settings.png

一度 docker-compose down を行います。

docker-env を編集してください。

docker-env
...

  # Change Require
  CMD_DB_URL=mysql://hackmd:hackmdpass@database:3306/hackmd
  # Githubログイン使いたいなら
- # CMD_GITHUB_CLIENTID={YOUR_GITHUB_CLIENT_ID}
- # CMD_GITHUB_CLIENTSECRET={YOUR_GITHUB_SECRET_KEY}
+ CMD_GITHUB_CLIENTID=SampeClientIdDesu
+ CMD_GITHUB_CLIENTSECRET=SampeSecretKeyDesu

  # option: Docker Environment
  # MYSQL_USER=
  # MYSQL_PASSWORD=
  # MYSQL_DATABASE=
  # MYSQL_ROOT_PASSWORD=
  # APP_USE_PORT=

編集が完了したら docker-compose up -d をすると
先程は無かった サインイン のボタンが現れるようになります。

試しにログインをすると、Githubからユーザー名などを取れておりログインできてる事がわかります。
image.png

EC2で動かしてみよう(ホスト)

Ubuntu でざっくりと docker を使用せずに構築したいと思います。
基本的なセキュリティの設定などは各々あると思いますので省かせていただきます。

すでにEC2にsshしている状態でのお話になります。

Step1. 事前準備

  • apt update upgrade
EC2インスタンス[ubuntuユーザー]
$ sudo apt update
$ sudo apt upgrade

Step2. ユーザーの作成とsudoグループ追加

EC2インスタンス[ubuntuユーザー]
# ユーザーの作成
$ sudo adduser hackmd
    Adding user `hackmd' ...
    Adding new group `hackmd' (1001) ...
    Adding new user `hackmd' (1001) with group `hackmd' ...
    Creating home directory `/home/hackmd' ...
    Copying files from `/etc/skel' ...
    Enter new UNIX password: passwordを入力...
    Retype new UNIX password: passwordを入力...
    passwd: password updated successfully
    Changing the user information for hackmd
    Enter the new value, or press ENTER for the default
        Full Name []:
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:

# sudo グループへ hackmd ユーザーを追加
$ sudo gpasswd -a hackmd sudo

# スイッチッ!!!!!!
$ sudo su - hackmd

Step3. 必要なパッケージのインストール

  • コアになりそうなもののインストール
EC2インスタンス[hackmdユーザー]
[hackmd]$ sudo apt-get install -y git build-essential wget vim unzip libssl-dev python

  • nvm のインストール
EC2インスタンス[hackmdユーザー]
[hackmd]$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.0/install.sh | bash
[hackmd]$ export NVM_DIR="$HOME/.nvm"
[hackmd]$ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[hackmd]$ [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
[hackmd]$ source ~/.bashrc
[hackmd]$ nvm --version
0.35.0
  • node のインストール
EC2インスタンス[hackmdユーザー]
[hackmd]$ nvm install v11.14.0
[hackmd]$ nvm use v11.14.0
[hackmd]$ node -v
11.14.0
  • yarn のインストール
EC2インスタンス[hackmdユーザー]
[hackmd]$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
[hackmd]$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
[hackmd]$ sudo apt-get update && sudo apt-get -y install yarn
[hackmd]$ export PATH="$PATH:/opt/yarn-1.15.2/bin"
[hackmd]$ source ~/.bashrc
[hackmd]$ yarn -v
1.15.2
  • mariaDBインストール(MySQLでも良い)
EC2インスタンス[hackmdユーザー]
[hackmd]$ curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
[hackmd]$ sudo apt -y update
[hackmd]$ apt install -y mariadb-server-10.3
[hackmd]$ mysql_secure_installation


        Enter current password for root (enter for none):
        > パスワード

        Change the root password? [Y/n]
        > Y
        > New password: パスワード
        > Re-enter new password: パスワード

        Remove anonymous users? [Y/n]
        > n

        Disallow root login remotely? [Y/n]
        > Y

        Remove test database and access to it? [Y/n]
        > Y

        Reload privilege tables now? [Y/n]
        > Y

[hackmd]$ mysql -uroot -p -h 127.0.0.1 -e "show databases;"
> ルートパスワード

        +--------------------+
        | Database           |
        +--------------------+
        | information_schema |
        | mysql              |
        | performance_schema |
        +--------------------+

# ユーザー作成
[hackmd]$ mysql -uroot -p -h 127.0.0.1 -e "CREATE USER ユーザー名 IDENTIFIED BY 'パスワード';"

# 権限付与
[hackmd]$ mysql -uroot -p -h 127.0.0.1 -e "GRANT ALL ON *.* TO 'ユーザー名'@'localhost' IDENTIFIED BY 'パスワード';"

# 適用
[hackmd]$ mysql -uroot -p -h 127.0.0.1 -e "FLUSH PRIVILEGES;"

[hackmd]$ mysql -uユーザー名 -p -h 127.0.0.1 -e "create database データベース名;"
  • nginx
EC2インスタンス[hackmdユーザー]
[hackmd]$ curl http://nginx.org/keys/nginx_signing.key | sudo apt-key add -
[hackmd]$ VCNAME=`cat /etc/lsb-release | grep DISTRIB_CODENAME | cut -d= -f2` && sudo -E sh -c "echo \"deb http://nginx.org/packages/ubuntu/ $VCNAME nginx\" >> /etc/apt/sources.list"
[hackmd]$ VCNAME=`cat /etc/lsb-release | grep DISTRIB_CODENAME | cut -d= -f2` && sudo -E sh -c "echo \"deb-src http://nginx.org/packages/ubuntu/ $VCNAME nginx\" >> /etc/apt/sources.list"
[hackmd]$ sudo apt-get update
[hackmd]$ sudo apt-get install -y nginx

Step4. リポジトリのクローンと設定

github の .netrc などの設定は一旦省かせていただきます。
また config.json や sequelizerc はローカルのときとほとんど同じとなっていますのでSKIPしていただいても構いません。
ただ、SSLの関係上環境変数などが若干増えています。

EC2インスタンス[hackmdユーザー]
[hackmd]$ git clone https://github.com/hackmdio/codimd.git
[hackmd]$ cd codimd
[hackmd]$ cp .sequelizerc.example .sequelizerc
[hackmd]$ cp config.json.example config.json

  • utf8mb4.cnf の設定
EC2インスタンス[hackmdユーザー]
[hackmd]$ sudo vim /etc/mysql/mariadb.conf.d/utf8mb4.cnf

/etc/mysql/mariadb.conf.d/utf8mb4.cnf
    # MariaDB-specific config file.
    # Read by /etc/mysql/my.cnf

    [client]
    default-character-set=utf8mb4

    [mysql]
    default-character-set=utf8mb4

    [mysqld]
    collation-server=utf8mb4_general_ci
    init-connect='SET NAMES utf8mb4'
    character-set-server=utf8mb4

    # Import all .cnf files from configuration directory
    !includedir /etc/mysql/mariadb.conf.d/

  • config.json の設定

  • 画像は S3 へ アップロード

  • ログインは メール登録を禁止しGithubログインのみ

  • ゲストユーザーは書き込み閲覧を出来ない

  • ドメインの指定

という形でconfigを作っていきます。

SSLはこの段階では行わないが後述で行います。

Github の

EC2インスタンス[hackmdユーザー]
[hackmd]$ vim config.json

config.json

{
  "production": {
    "loglevel": "info",
    "useSSL": false,
    "protocolUseSSL": false,
    "domain": "xxxxxxxxxxx.com", #--ドメインがあるのであれば
    "urlAddPort": "80",
    "allowAnonymousEdits": false,
    "defaultPermission": "private",
    "allowEmailRegister": false,
    "email": false,
    "allowGravatar": true,
    "db": {
      "username": "xxxxxxxxxxx",
      "password": "xxxxxxxxxxx",
      "database": "xxxxxxxxxxx",
      "host": "127.0.0.1",
      "port": "3306",
      "dialect": "mysql"
    },
    "github": {
      "clientID": "xxxxxxxxxxxxxxxxxxxxxx",
      "clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    },
    "imageuploadtype": "s3",
    "s3": {
      "accessKeyId": "xxxxxxxxxxxxxxxxxxxxxx",
      "secretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "region": "ap-northeast-1"
    },
    "s3bucket": "codimd-production"
  }
}

  • nginx の設定
EC2インスタンス[hackmdユーザー]
[hackmd]$ sudo vim /etc/nginx/conf.d/node-app.conf

/etc/nginx/conf.d/node-app.conf
server {
  listen 80;
  # listen  443 ssl;
  ssl on;

  # 編集すること
  server_name  {YOUR_DOMAIN}.com;

  # 今はまだ使わない
  # ssl_certificate      /etc/letsencrypt/live/{YOUR_DOMAIN}.com/fullchain.pem;
  # ssl_certificate_key  /etc/letsencrypt/live/{YOUR_DOMAIN}.com/privkey.pem;

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

  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 https://localhost:3000;
      proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
  }

  # あとで SSL取得に使う
  location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    root         /home/hackmd/codimd/public;
  }

  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /home/hackmd/codimd/public;
  }
}

EC2インスタンス[hackmdユーザー]
[hackmd]$ sudo systemctl restart nginx

  • 無料枠だとRAMに限界あるので SWAP領域を作成
EC2インスタンス[hackmdユーザー]
[hackmd]$  sudo dd if=/dev/zero of=/swap bs=1M count=1024
[hackmd]$  sudo mkswap /swap
[hackmd]$  sudo swapon /swap
[hackmd]$  sudo chmod 600 /swap

Step5. 立ち上げ

EC2インスタンス[hackmdユーザー]
[hackmd:~/codimd]$ yarn install
[hackmd:~/codimd]$ yarn global add webpack
[hackmd:~/codimd]$ npm run build
[hackmd:~/codimd]$ node_modules/.bin/sequelize db:migrate
[hackmd:~/codimd]$ node app.js

Step6. 永続的にappを立ち上げさせる

EC2インスタンス[hackmdユーザー]
[hackmd:~/codimd]$ npm install -g forever
[hackmd:~/codimd]$ forever start app.js

Step7. SSL化(HTTPS)

今回は Let's Encrypt を利用します

  • certbot challenge
EC2インスタンス[hackmdユーザー]
[hackmd:~/codimd]$ mkdir -p .well-known/acme-challenge/
[hackmd:~/codimd]$ chmod 644 .well-known/acme-challenge/
[hackmd:~/codimd]$ cd ~/
[hackmd:~/]$ git clone https://github.com/certbot/certbot.git && cd certbot
[hackmd:~/]$ sudo ./certbot-auto certonly --webroot -w /home/hackmd/codimd/public -d {YOUR_DOMAIN}.com -m {YOUR_MAIL_ADDR} --debug

  • application側の対応
EC2インスタンス[hackmdユーザー]
[hackmd:~/]$ cd codimd

  • 環境変数の変更
EC2インスタンス[hackmdユーザー]
[hackmd:~/codimd]$ direnv edit .

.ervrc
- export CMD_PROTOCOL_USESSL=false
+ export CMD_PROTOCOL_USESSL=true

  • config.json の変更
EC2インスタンス[hackmdユーザー]
[hackmd:~/codimd]$ vim config.json

config.json
{
  "production": {
    "loglevel": "info",
-   "useSSL": false,
-   "protocolUseSSL": false,
+   "useSSL": true,
+   "protocolUseSSL": true,
+   "sslKeyPath": "/etc/letsencrypt/live/xxxxxxxxxxx.com/privkey.pem",
+   "sslCertPath": "/etc/letsencrypt/live/xxxxxxxxxxx.com/cert.pem",
+   "dhParamPath": "/etc/letsencrypt/live/xxxxxxxxxxx.com/fullchain.pem",
+   "sslCAPath": ["/etc/letsencrypt/live/xxxxxxxxxxx.com/chain.pem"],
    "domain": "xxxxxxxxxxx.com", #--ドメインがあるのであれば
    "urlAddPort": "80",
    "allowAnonymousEdits": false,
    "defaultPermission": "private",
    "allowEmailRegister": false,
    "email": false,
    "allowGravatar": true,
    "db": {
      "username": "xxxxxxxxxxx",
      "password": "xxxxxxxxxxx",
      "database": "xxxxxxxxxxx",
      "host": "127.0.0.1",
      "port": "3306",
      "dialect": "mysql"
    },
    "github": {
      "clientID": "xxxxxxxxxxxxxxxxxxxxxx",
      "clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    },
    "imageuploadtype": "s3",
    "s3": {
      "accessKeyId": "xxxxxxxxxxxxxxxxxxxxxx",
      "secretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "region": "ap-northeast-1"
    },
    "s3bucket": "codimd-production"
  }
}

  • nginx の変更
EC2インスタンス[hackmdユーザー]
[hackmd:~/codimd]$ sudo vim /etc/nginx/conf.d/node-app.conf

/etc/nginx/conf.d/node-app.conf
upstream node-app {
  server localhost:3000;
}

server {
  listen  443 ssl;
  ssl on;

  server_name  xxxxxxxxxxxx.com;

  ssl_certificate      /etc/letsencrypt/live/xxxxxxxxxxx.com/fullchain.pem;
  ssl_certificate_key  /etc/letsencrypt/live/xxxxxxxxxxx.com/privkey.pem;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

  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://node-app/;
  }

  location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    root         /home/hackmd/codimd/public;
  }

  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /home/hackmd/codimd/public;
  }
}

EC2インスタンス[hackmdユーザー]
[hackmd:~/codimd]$ sudo systemctl restart nginx

Step8. Restart

EC2インスタンス[hackmdユーザー]
[hackmd:~/codimd]$ forever app.js restart

アクセス!

image.png

Github の New OAuth App はSSLが出来たタイミング等で先程と同じ手順で行ってください。

ちゃんと鍵も出てるし ログインも出来る〜。 \ 最 高 /

トラブルシューティング

  1. SSL化にて challenge に失敗した場合、原因として .well-known/acme-challenge/ にアクセス出来てない可能性があります。
    • chmod 644 .well-known/acme-challenge/ などをしてみてください。
    • また、 challenge 時 80番(HTTP)でアクセスが来るはずなので 更新の際は listen 80 にするのを忘れずに
  2. 今回はドメインの設定を公開してないですが タイプA の設定をしています。(google で買ったドメインを利用しています。)

チームで使っている板を自動でQiita Teamに投稿できるようにしてみた

私達のチームでは、1つの板を専有し毎日更新し続けています。
その板に書いているものは以下の通りとなっております。

  • 共有事項
    • 有給取る日やフレックス出社日など
  • 今日のゴール
    • 今日終わらせるべきタスクを明確にする
  • 作業ログ
    • 作業を行う際、個人個人がそれぞれh2でタイトルを切ってログ等をまとめたりする

今までは翌日の朝にこれらを全コピしてQiita Teamに転記していたのですが、
まあちょっとの繰り返しさえも自動化したくなるのがエンジニア魂ですよね(ㆁωㆁ*) めんどくさいだけ

やり方は簡単で Qiita API を使ったスクリプトを Cron で平日毎日 22:00 頃に回すだけです。

sampleコードを載せておきます。

post_daily_report_by_cosmos_team.rb
require 'net/https'
require 'mysql2'
require "json"
require "date"

mysql_username = ENV['MYSQL_USERNAME'].freeze
mysql_password = ENV['MYSQL_PASSWORD'].freeze
mysql_database = ENV['MYSQL_DATABASE'].freeze
team_note_id   = ENV['TEAM_NOTE_ID'].freeze
qiita_api_key  = ENV['QIITA_API_KEY'].freeze
domain         = ENV['TEAM_DOMAIN'].freeze

client = Mysql2::Client.new(
  socket: '/var/run/mysqld/mysqld.sock',
  username: mysql_username,
  password: mysql_password,
  encoding: 'utf8',
  database: mysql_database
)

statement = client.prepare('SELECT content FROM Notes WHERE id = ?')
# 事前にタイトルで select しておき ID を控えています。
# example: 
#   mysql -uhackmd -p hackmd -e "select id from Notes where title = 'cosmos開発日報'"
results = statement.execute(team_note_id)

weekday = ["日", "月", "火", "水", "木", "金", "土"]
today = Date.today
title = "cosmos開発日報" + today.strftime("%Y/%m/%d (#{weekday[today.wday]})")
content = ""

results.each do |row|
  content = row["content"]
end

# 組み立て
qiita = "https://#{domain}.qiita.com/"
path = '/api/v2/items'
url = qiita + path

uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)

http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

req = Net::HTTP::Post.new(uri.request_uri)
req["Authorization"] = "Bearer #{qiita_api_key}"
req["Content-Type"] = "application/json"

post_data = {
  title: title,
  body: content,
  coediting: true,
  group_url_name: "cosmos",
  private: false,
  tags: [{name: "cosmos"},{name: "日報"}],
  tweet: false,
}.to_json

# 投稿
req.body = post_data
# 結果
res = http.request(req)
puts res.code, res.msg

crontab
# Edit this file to introduce tasks to be run by cron.
00 13 * * 1-5 bash -cl 'cd /home/hackmd/cron_scripts/ && ruby -Ku post_daily_report_by_cosmos_team.rb'

image.png

毎日こんな感じで投稿されるようになりました! \ 自 動 化 バ ン ザ イ /

こんな感じで好きな機能を追加したりをしてみたり実験場としても活躍しています!

最後に

今回は docker で構築する方法と ホスト上に直接構築する方法の2種類を紹介いたしました!
応用すれば ECS にすることも出来ますね(ㆁωㆁ*)

エンジニアとして、「あのサービスにこの機能があったら最高なんだけど...」って思うことって多々ありますよね。

もともと利用してたHackMDをそのまま改造出来るって最高だなって思いました。 THE 幸せ

HackMDはエンタープライズ版があるとお話していましたが、エンタープライズ版では高頻度で504になり 自動復旧を待つ ということが高頻度で起きました。
連続で起きるときもあれば起きないときもあり、不安定な状態が続いた為オンプレ化を進めたのが実背景となっています。

実際やってみるとそこまで難しいことはなく、 機能の拡張 なども容易に行えてしばらくは遊べそうです(ㆁωㆁ*)

料金も EC2 インスタンスの無料枠(t2.micro)を使っていますが、アクティブユーザーは最大15人とかでも落ちるといったことは今までありませんでした。

画像もS3にあげているので容量圧迫とかも起きず快適に利用できています!

文字だけなのでmysql自体の負荷も少ないことから、 社内で50人くらいで使う分には無料枠で全然問題ない かと思われます。

ぜひ皆様も CodiMD を利用してみてくださいね(ㆁωㆁ*) bye

? hackmdio/codimd: CodiMD - Realtime collaborative markdown notes on all platforms.

またもっと良いツールがあるよ!って方はぜひ教えて下さい!

Twitterもやってるのでぜひフォローください。
? つなまよ(@mayoxtuna)さん / Twitter
Apex Legends(PC版)とか一緒に趣味開発してくれるお友達募集中です٩(๑`ω´๑)۶

次回予告

明日は クリスマス・イブ ですね٩(๑`ω´๑)۶ ? :.。.:May your Christmas wishes come true:.。.: ?

12月24日は クラウドワークスエンジニアである @cesare による 「複業を支える技術」です!
お楽しみに〜bye

2019/12/24 追記

この記事を公開したあと Twitter 経由で HackMD の CEO の方から直々に感謝のDMを頂きました。
そこで エンタープライズ版の 504 になる問題について教えていただきました。

現在では高パフォーマンスで動く状態を維持しており、それらの問題は起きないとのことでした (ㆁωㆁ*)

日々進化し続ける HackMD 最高ですね٩(๑`ω´๑)۶

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQL Workbenchに日本語CSVをインポート

よくある話

必要に迫られMySQLにAccessデータをインポートする事になり、四苦八苦した話です。ほぼ備忘録ですが、同じところで躓いている方のお役に立てれば。(類似記事は多かったけれど、中々自分のパターンにあてはまるものが無かった)

環境

・Windows10 Enterprise
・Docker Desktop for Windows:2.1.0.1
・MySQL:5.7.28
・MySQL Workbench 8.0

概要

・Docker ComposeでMySQLコンテナをビルド
・MySQL WorkBenchでコンテナのDBに接続し編集
・Accessからcsvエクスポート(UTF-8)したものをMySQLにインポート

インポート失敗

MySQL Workbenchのメニュー「Database」→「Migration Wizard」はちょっと面倒そうだったので、左メニューのインポートしたいデータベース名を右クリック→「Table Data Import Wizard」でインポート作業。
サクッといけると思ったけれど日本語が入っているとエラーでインポートできない。
テスト用にCSVで簡単なテーブルを作ってインポートしたが、1文字でも日本語が入っているレコードはインポートされなかった。

デフォルト文字コード変更

色々調べた中で、こちら↓の記事の内容で解消。助かりました。
MySQLでcharacter_set_databaseがlatin1になってしまう問題の対応方法

MySQLにログインした状態でコマンド「show variables like 'char%';」実行した際に表示される各文字コードの中で、character_set_databaseが「latin1」から変更出来ないのが原因だった。下記手順にて設定変更に成功した。

手順

1:mysqlログイン
2:コマンド「use {インポート先のDB名}」でDB指定
3:コマンド「show variables like 'char%';」実行 → character_set_databaseが「latin1」になっている事を確認
4:コマンド「show create database `{インポート先のDB名}`;」 → /*!40100 DEFAULT CHARACTER SET latin1 */になっている
5:「ALTER DATABASE `{インポート先のDB名}` default character set utf8;」実行し、デフォルトをUTF8に変更
6:コマンド「show variables like 'char%';」実行 → character_set_databaseが「utf8」に変更された事を確認
7:MySQL WorkBenchで日本語含むcsvのインポート成功

おわりに

業務逼迫中のメモなので図も無いざっくりで申し訳ないですが、また余裕出来たらちゃんと書き直したいと思います。読んで頂いてありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQL Workbenchで日本語CSVをインポート

よくある話

必要に迫られMySQLにAccessデータをインポートする事になり、四苦八苦した話です。ほぼ備忘録ですが、同じところで躓いている方のお役に立てれば。(類似記事は多かったけれど、中々自分のパターンにあてはまるものが無かった)

環境

・Windows10 Enterprise
・Docker Desktop for Windows:2.1.0.1
・MySQL:5.7.28
・MySQL Workbench 8.0

概要

・Docker ComposeでMySQLコンテナをビルド
・MySQL WorkBenchでコンテナのDBに接続し編集
・Accessからcsvエクスポート(UTF-8)したものをMySQLにインポート

インポート失敗

MySQL Workbenchのメニュー「Database」→「Migration Wizard」はちょっと面倒そうだったので、左メニューのインポートしたいデータベース名を右クリック→「Table Data Import Wizard」でインポート作業。
サクッといけると思ったけれど日本語が入っているとエラーでインポートできない。
テスト用にCSVで簡単なテーブルを作ってインポートしたが、1文字でも日本語が入っているレコードはインポートされなかった。

デフォルト文字コード変更

色々調べた中で、こちら↓の記事の内容で解消。助かりました。
MySQLでcharacter_set_databaseがlatin1になってしまう問題の対応方法

MySQLにログインした状態でコマンド「show variables like 'char%';」実行した際に表示される各文字コードの中で、character_set_databaseが「latin1」から変更出来ないのが原因だった。下記手順にて設定変更に成功した。

手順

1:mysqlログイン
2:コマンド「use {インポート先のDB名}」でDB指定
3:コマンド「show variables like 'char%';」実行 → character_set_databaseが「latin1」になっている事を確認
4:コマンド「show create database `{インポート先のDB名}`;」 → /*!40100 DEFAULT CHARACTER SET latin1 */になっている
5:「ALTER DATABASE `{インポート先のDB名}` default character set utf8;」実行し、デフォルトをUTF8に変更
6:コマンド「show variables like 'char%';」実行 → character_set_databaseが「utf8」に変更された事を確認
7:MySQL WorkBenchで日本語含むcsvのインポート成功

おわりに

業務逼迫中のメモなので図も無いざっくりで申し訳ないですが、また余裕出来たらちゃんと書き直したいと思います。読んで頂いてありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Private Docker Container Registryサービスを作った話

こんにちは、さくらインターネットのこたまごです。

今日は、今日リリースしたサービス「Sakura Container Registry」のお話をします。

Private Container Registryとは

Dockerなどのコンテナランタイムでは、実行するコンテナのイメージをどこからか取得する必要があります。
このイメージを保管する機能がContainer Registryです。

最も利用されていると思われるのは Docker Hub でしょうか。NginxやPHP、PostgreSQLなど、公式、非公式問わずたくさんのイメージが共有され公開されています。

基本的にこれらのイメージは誰でもダウンロードすることができます。

一方で、自分だけがアクセスできるContainer RegistryがPrivate Container Registryです。
Docker Hubでも、有料プランを利用することで自分だけのイメージを保管することができます。

昨今コンテナの利用が進む中、意外とめんどくさいのがこのリポジトリの用意と運用です。

Sakura Container Registryの使い方

さくらのクラウド ドキュメントもご参照ください。

レジストリの作成

さくらのクラウドにログインし、メニューから「コンテナレジストリ」→「作成」ボタンをクリック。

レジストリ一覧

コンテナレジストリ名と公開設定が重要なポイントです。

コンテナレジストリ名は一度設定すると変更できません。公開設定はログインしていないユーザーのアクセス権を設定することができます。

  • 「Push & Pull」イメージの更新も誰でもできるレジストリになります
  • 「Pullのみ」イメージの更新は認証が必要ですが、イメージを取得し利用するのは誰でもできるレジストリになります
  • 「非公開」イメージの更新も取得も認証が必要なレジストリになります

現時点では独自ドメインは利用できませんが、近々対応したいと思っています。

レジストリ作成画面

コンテナレジストリ名を決めて作成すると「example.sakuracr.jp」のようなドメインが割り当てられます。これは後から変更できません。

ユーザーの作成

実際に使う前にユーザーの追加が必要です。「ユーザー」タブから追加と削除ができるので追加してください。

詳細画面

ユーザ一覧

ユーザ作成

現時点では読み書き (Push & Pullどちらもできる) 権限のあるユーザーしか作成できませんが、今後読み込み (Pull) 専用ユーザーも用意しようと思います。

Dockerから使う

まずログインをしておく必要があります。

$ docker login example.sakuracr.jp
Username: (作ったユーザーのユーザー名)
Password: (パスワード)

Login Succeeded と表示されればOKです。

次に、アップロード(push)したいイメージを作成します。イメージ名は必ず example.sakruacr.jp/(好きな名前):(タグ) としてください。

ex) example.sakuracr.jp/nginx:latest, example.sakuracr.jp/awsomeproject/application:latest

今回は例としてNginxのイメージをそのまま利用します。

$ docker pull nginx:latest
$ docker tag nginx:latest example.sakuracr.jp/nginx:latest

Dockerfile がある場合には

$ docker build -t example.sakuracr.jp/awsomeproject/application:latest

などとしましょう。

あとはpushするだけです。

$ docker push example.sakuracr.jp/nginx:latest

or

$ docker push example.sakuracr.jp/awsomeproject/application:latest

サービスの仕組み

Docker Distributionをベースに、マルチテナントなContainer Registryを開発しました。

  • 認証管理システム
  • Container Registry
  • ストレージ
  • キャッシュ

の4つのアプリケーションで構成されています。

diagram.png

認証管理システム

どんなレジストリがあるか、どんなユーザーがあるかを管理しています。
また、実際の認証認可も行っており、 docker login コマンド等の要求に対してJWT (JSON Web Tokens) で署名された認証キーの発行をしています。

私の趣味によりPythonとDjangoで書かれています。

Container Registry

docker push docker pull コマンドで実際に通信するサーバです。

HTTPSのみに対応しており、SNIによってホスト名を識別後、ホスト名に応じて挙動が変わるようになっています。
これ自身にはパスワード認証の機能はなく、全て上記の認証管理システムから発行されたJWTによって認証認可を行っています。

Goで書かれています。

ストレージ

docker push されたイメージを全て保存しているストレージです。社内の分散ストレージを利用しています。

キャッシュ

docker pull が実行されたとき、イメージによってはかなり大きなデータ量の読み出しと送信が発生します。
また、Container Registryの特性上、分散したRegistryサーバに対して同じイメージが並列でリクエストされることが想定されます。

そこで、Container Registryとストレージの間にキャッシュサーバを挟み、イメージのキャッシュを行っています。

ハッシュ値によってキャッシュするサーバを決定することで、多数あるContainer Registryからの同じデータへのアクセスが一箇所のキャッシュサーバに集中するような工夫をしています。

Goで書かれています。

さいごに

本サービスは、さくらインターネット内に新たにできたチーム「エンジニアリングラボ」で開発されたサービスです。
エンジニアリングラボでは品質に固執せず、開発早期の段階でお客様にご提供し、使ってみていただけるような取組を行うことを目的としております。

これからもさくらのエンジニアリングラボでは様々なサービスをお届けしていきます。

うまくいけばそのまま本番サービスとして提供できるものもあれば、お試し提供中に終了となるサービスも生まれるかもしれません。
ワクワクするようなたくさんの体験を皆さんに提供できるように努力していきますので、どうぞよろしくお願いいたします。

できるまで

「開発早期の段階でお客様にご提供する」精神で、開発開始から17日でリリース!

  • 12/2 レジストリが欲しいと弊社社員が言う
  • 12/6 調査開始
  • 12/7 レジストリ実装
  • 12/8 管理システム実装
  • 12/9 コンパネ実装
  • 12/19 本番環境APIリリース
  • 12/23 本番環境コンパネリリース
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker開発環境のDBにReadOnlyユーザーを作り、書き込みバグを検知する

この記事は、Docker Advent Calendar 2019の19日目の記事です。

TL;DR

  • Dockerを開発環境で使う際に、replica databaseを使うようなアプリケーションにて、書き込みバグを検知する
  • MySQLのデータベースにreadOnlyユーザーを作成してアプリケーションから利用する
  • レプリカラグの考慮などには対応できないが、最低限書き込みバグを防ぐことができる

ReadOnlyユーザーを作る

Webアプリケーション開発において、PostgreSQLやMySQLといったRDBMSをシステムのデータベースとして使うケースは多いです。そのような構成において、負荷対策で参照クエリを逃がすと行った用途でReplicaインスタンス(読み込み専用)を用意することは多いでしょう。

このシステム構成で、Docker開発環境を作る場合、データベースサービスをローカルのDockerコンテナとして立てる際、間違えてReplicaデータベースに書き込むバグ作っちゃったとはならないようにしたいですよね。この目的感のもと採用できる打ち手の一つが、ReadOnlyユーザーを作成する方法です。

この方法は、すぐに実践できる手軽さもあり、それなりの数の実践事例を観測しています。たとえば、CakeFest 2019というCakePHPの国際カンファレンスでの「Working with Database Replications in CakePHP」という発表の中でも、レプリカデータベースに対して書き込んでしまうようなアプリケーションバグを検知するために、ReadOnlyユーザーを作成するという話をしていました。

実際に、筆者の現場でも同様の方法を採用しています。この方法はレプリケーションラグの考慮などを踏まえると完全なシミュレーション方法ではありませんが、初手としては良い方法と考えています。

実現例

今回実現する例は、次のような構成です。

  • MySQLを利用、MySQLのimageは公式のものを利用します
  • 複数コンテナサービスが必要なため docker-compose を用います

例題として取り上げるものは下記のGitHub repositoryに公開しています。

https://github.com/hgsgtk/health-endpoint

├── docker-compose.yml
├── docker
│   └── db
│       ├── conf.d
│       │   └── custom_my.cnf
│       └── docker-entrypoint-initdb.d
│           ├── 1_initialize_tables.sql
│           └── 2_readuser.sql
└── src
    └── Dockerfile

docker-compose.yml

早速、docker-compose.ymlを見てみましょう。Compose file formatはversion 3を用いています。

refs: https://docs.docker.com/compose/compose-file/

docker-compose.yml
version: '3'

services:
  health-api:
    build:
      context: "./src/"
    ports:
      - "8080:8080"
    depends_on:
      - db
    restart: on-failure
    env_file:
      - ".env"

  db:
    image: mysql:5.6
    ports:
      - "3306:3306"
    expose:
      - 3306
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: healthy
      MYSQL_USER: wruser
      MYSQL_PASSWORD: password
    volumes:
      - ./docker/db/data:/var/lib/mysql:cached
      - ./docker/db/conf.d:/etc/mysql/conf.d:cached
      - ./docker/db/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:cached

話を単純にするために2つのサービスのみを取り上げます。Web APIであるheath-apiとMySQLデータベースであるdbを定義しています。

mysql公式イメージの使い方のおさらい

環境変数

mysqlの公式イメージの使い方をざっとおさらいすると、MYSQL_ROOT_PASSWORDなど特定の環境変数を指定することで作成されるrootユーザーのパスワードなどを指定することが出来ます。

https://hub.docker.com/_/mysqlEnvironment Variablesを見ることで環境変数で設定できる項目を確認することが出来ます。

データの永続化

コンテナが停止したときにデータが消えてしまうと開発環境としては少し物足りませんね。データをコンテナ停止しても永続化された状態にするために、次の記述でボリュームマウントします。

docker-compose.yml
    volumes:
      - ./docker/db/data:/var/lib/mysql:cached

これは、 https://hub.docker.com/_/mysqlCaveats > Where to Store Dataに説明されています。Git管理下におくとMySQLのデータがそのままコミットされてしまうので、対象ディレクトリを.gitignoreに追加しておくのを忘れないようにしましょう。

.gitignore
# docker database local mount files
docker/db/data

Custom MySQL Configuration

デフォルトの設定ではなく、ユーザー自身でMySQLの設定をカスタムしたいという場合は、コンテナ内の/etc/mysql/conf.dに設定をマウントします。これは、 https://hub.docker.com/_/mysqlUsing a custom MySQL configuration file に説明されています。

docker-compose.yml
    volumes:
      - ./docker/db/conf.d:/etc/mysql/conf.d:cached

例えば、このケースではつぎのようなmy.cnfを設定します。

docker/db/cnf.d/custom_my.cnf
[mysqld]
explicit_defaults_for_timestamp=1
symbolic-links=0
sql_mode=TRADITIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY
character-set-server=utf8

[client]
default-character-set=utf8

ReadOnlyユーザーを設定する

ReadOnlyユーザーを設定します。docker-compose.ymlでは次の設定項目が該当します。

docker-compose.yml
    volumes:
      - ./docker/db/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d:cached

まずは、docker-entrypoint-initdb.dについてですが、コンテナ内の/docker-entrypoint-initdb.d.sh.sql.sql.gz拡張子のファイルをおくと、初期化の実行してくれます。

そのため、この初期化時にReadOnlyユーザーを作るSQLを実行すれば、ReadOnlyユーザーを作成できます。

docker/db/docker-entrypoint-initdb.d/2_readuser.sql
GRANT SELECT, PROCESS ON *.* TO 'reader'@'%' IDENTIFIED BY 'password';

ファイル名ですが、

Files will be executed in alphabetical order.

という記述が、https://hub.docker.com/_/mysqlInitializing a fresh instanceにあることが理由です。アルファベット順にソートされ実行されるので、明示的に順番になるように数字を先頭にしています。

実際にReadOnlyユーザーが作れているか

実際に、rootアカウントでデータベースにアクセスし、ユーザーが作られているか確認します。mysql.userテーブルに対して検索すると次のような結果が得られます。

SELECT 
    User
FROM
    mysql.user;
reader
root
wruser
root

作成したReadOnlyユーザーであるreaderを確認することが出来ます。このユーザーには、GRANTで権限を付けているので、SELECTPROCESSが可能なユーザーになっています。

SELECT 
    User,
    Select_priv,
    Insert_priv,
    Update_priv,
    Delete_priv,
    Process_priv
FROM
    mysql.user
WHERE
    User = 'reader';
reader  Y   N   N   N   Y

これで、このデータベースを使うコンテナサービスが、レプリカ接続にReadOnlyユーザーを使うことで、誤って書き込みがおこなれても無事失敗するようになります。

ReadOnlyユーザーで書き込みをした場合
INSERT INTO customers VALUES (1, now(), now())
INSERT command denied to user 'reader'@'172.27.0.1' for table 'customers'

余談: test_データベース

MySQL、とくに5.6の挙動に詳しい方であれば、ReadOnlyユーザーと聞いたときに、「テスト用データベースの場合の考慮はいらないだろうか」と疑問に思うかもしれません。MySQL 5.6の挙動として、次のような仕様があります。

さらに、mysql.db テーブルにはすべてのアカウントが test データベースおよび test_ で始まる名前を持つその他のデータベースにアクセスすることを許可する行が含まれます。これは、デフォルトの匿名アカウントのように、そうでなければ特別な権限を持たないアカウントにも当てはまります。

https://dev.mysql.com/doc/refman/5.6/ja/default-privileges.html

この仕様のままであれば、testあるいはtest_で始まるデータベースの場合、ReadOnlyユーザーの権限を作っても書き込めてしまうことになります。これに対して、MySQL管理者への推奨は、

データベースへのアクセスを、その目的のために明示的に許可を付与されたアカウントのみに制限する場合は、管理者は mysql.db テーブルのこれらの行を削除するとよいでしょう。

とある通り、mysql.dbテーブルから該当行を削除することでこの挙動を無効にする対応が必要と説明しています。

これは、MySQLの公式イメージでは実際意識する必要はありません。なぜなら、イメージのdocker-entrypoint.shにて、該当行の削除をしてくれているからです。

DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%' ;

https://github.com/docker-library/mysql/blob/6659750146b7a6b91a96c786729b4d482cf49fe6/5.6/docker-entrypoint.sh#L241

公式イメージを使用すると、このようなコミュニティのナレッジを対応してくれているものを利用できるのが利点ですね。

最後に

ReadOnlyユーザーを作る方法は、比較的手軽に実践できるので、まだやっていない方は、一つの選択肢として検討してみてはいかがでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

wordpressの環境で2MBの制限をうける!(docker編)

あんまりこの編詳しくないので忘れないように覚書。
https://qiita.com/irico/items/64391ef45e5d71dde147
↑上記の手順でwpのローカル環境を構築した場合を想定。

2MBの制限

All-in-One WP MigrationでWPの環境をローカルに持ってこようとしたら、
最大アップロードファイルサイズ:2MB の部分で弾かれてしまいます。

解決法

dockerの中にphp.iniを作る。
そこでファイルアップロード制限の設定部分を変える。
ちなみに雛形があるのでそれを使いましょう。

docker exec -it {docker名} /bin/bash

でdocker内に入る。

cd ../../../usr/local/etc/php/

でphp.iniの雛形部分に移動。
lsを叩くと php.ini-developmentとphp.ini-productionがあります。
https://unskilled.site/dokcer%E3%81%AE%E5%85%AC%E5%BC%8Fwordpress%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%81%AEphp-ini%E3%81%AE%E5%A0%B4%E6%89%80/
のサイトで違いを参照されたし。
php.ini-developmentが色々エラーを出してくれます。でもセキュリティー等を考えると本番で使用するものはphp.ini-productionがいいようです。

cp php.ini-production php.ini

でphp.iniファイルを作りましょう。

vi php.ini

で中身を編集。

viコマンドが見つからない

https://qiita.com/YumaInaura/items/3432cc3f8a8553e05a6e
上記のサイトを参考にしました。
私はMacなので、

apt-get update
apt-get install vim

でvi/vimコマンドをインストール。

ファイル編集

/upload_max_filesizeでupload_max_filesizeが書かれた箇所を検索して、2Mの部分を編集しましょう。
こちらはアップロードの上限容量になります。
/post_max_size でpost_max_sizeも検索し、記述を変更しましょう。
こちらはPOSTデータの上限容量になります。

編集が終わったら、

exit

でdockerから抜けます。

あとはdockerを再起動すれば2MB制限が無くなります!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンテナデバッグの底力を感じてください 〜VSCodeの品格

概要

Dockerfileを用意してコンテナ上で開発するときの話。

  • コンテナ内にローカルのフォルダをマウントして、ローカルファイルを編集してもいいけど、コンテナ内のファイルを直接触りたい。でもvimでいつも触ってます。。

  • コンテナ内のC++ファイルをVSCodeでデバッグできたら嬉しいけど、結構めんどくさかったから諦めてしまいました。。

そんなあなたに朗報!!!
マイクロソフト大先生がすごい拡張機能を出してくれました!!素敵やん!!

VSCodeによる デバッグ環境の構築

ここでは、VSCodeを使って、

  1. コンテナ内にアクセスして、ローカルファイルと同じ感覚で編集する。
  2. 上の環境でC++のソースコードをデバッグ(ブレークポイントなど)する。 ことを目標とします。

1 コンテナにリモートアクセス

まず、以下のマイクロソフトのRemote Development という拡張機能をインストールしてください。

vsremote.png

インストールするとVSCodeの左下に緑のOpen a remote windowというボタン(下図)が出てくるので、クリックし、つなぎたいDockerコンテナを選択します。
vsremote1.png

すると、新しいウィンドウが開き、ここで編集するとコンテナ内のファイルを直接編集できます。
めっちゃ簡単やん、、すばらしい、、、、

2 C++ のデバックをVSCodeで行う

F1を押して、tasksと入力すると、configure tasks みたいなのが出てくるので、クリックするとtasks.json に飛びます。
そして、tasks.json

tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "echo",
            "type": "shell",
            "command": "自分がいつもc++ファイルをビルドするときに打つコマンドを書いてください〜 例)cmake. && make とか",
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

上のように編集します。このとき、コマンドは.vscodeフォルダがあるところからの相対パスで書くことに注意!
tasks.json を実行してc++のファイルをビルドしたいときは、Ctrl + Shift + B で実行できます。

さらに、F1から、launchとすると、launch.json を作ることができます。そこに、

launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "デバッグしたいビルド後のファイルへのパスを書きましょう〜",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

上のようにlaunch.jsonを書くと、左の虫のマークから、デバッグができます。(いつものように、F5から実行。)
このとき、コンテナ内にgdbが入っていることを確認してください。入っていないときは、

apt-get install gdb

で簡単にインストールできます。

結果 コンテナ上でC++のデバッグ簡単にできるやん!!

vsremote2.png

マイクロソフト先生ありがとう....

おわり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker for Windowsで "error during connect~" のエラーが出たときの対処法

Docker for WindowsをインストールしているWindows PCにて、PC起動時などに以下のダイアログが表示されることがあります。これの対処法。

image.png

誤った対処法

  1. とにかく「Reset to factory defaults」をクリックする
  2. 画面に従って処理を続ける

このボタン配置だと押したくなりますよね?私は押したくなる。「Upload crash Report」では何も解決しなそうだし、まさかQuitで何もせずに終了するはずもなく。
この「Reset to factory defaults」ボタン、要はdockerの初期化ボタンです。結果的に目先のエラーは直りますが、そもそもイメージとかビルドされたコンテナとかも全部なくなります
今までこちらの方法で対処してました。
ただ、コンテナ復活させるのにかなりの時間がかかります。

正しい対処法

  1. "Quit"を押してダイアログを閉じる
  2. WindowsのサービスからDockerサービス1を再起動する

これでだいたい直る、そうです。(ソースはこちら)
どうしても直らなかったら「Reset to factory defaults」が必要かもしれませんが、まずはサービスの再起動で直るかどうかを確認してみましょう。

まとめ

ダイアログが表示されてボタンがあるとつい押したくなってしまいますが、ボタンを押すのが必ずしも正とは限りません。
エラーのダイアログが表示されても冷静に対処しましょう。


  1. 私の環境では"com.docker.service"という名前でした 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker入門 〜Docker-compose, ネットワーク, ボリューム編〜

前回の記事
Docker入門 ~Dockerfile編~
の続きです。

Docker-compose

Docker-composeは、複数のコンテナを簡単に操作できる仕組みです。
使用したいコンテナの数が増えるとdockerコマンドをうつのが大変ですが、docker-composeコマンドで楽になります。

ファイル構成は以下とします。

.
├── docker-compose.yml
├── mysql
│   ├── Dockerfile
│   └── my.cnf
└── public
    └── index.html

mysqlディレクトリの内容
Dockerfile
FROM mysql:5.7.27

RUN apt-get update \
  && apt-get install -y locales \
  && rm -rf /var/lib/apt/lists/* \
  && echo 'ja_JP.UTF-8 UTF-8' >> /etc/locale.gen \
  && locale-gen ja_JP.UTF-8

ENV LANG ja_JP.UTF-8

COPY ./my.cnf /etc/mysql/conf.d/my.cnf
my.conf
[client]
default-character-set=utf8mb4

[mysqld]
character-set-server=utf8mb4

docker-compose.yml

docker-compose.ymlで、docker-composeコマンドでまとめて起動するコンテナの設定を記述します。

docker-compose.yml
version: '3.7'

services:
  php:
    image: my-php-apache:7.3.10
    ports:
      - 80:80
      - 443:443
    networks:
      - web
    volumes:
      - ./:/var/www

  mysql:
    build: ./dockerfiles/mysql
    expose:
      - 3306
    networks:
      - web
    volumes:
      - mysql:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=laravel
      - MYSQL_USER=laravel
      - MYSQL_PASSWORD=secret
      - MYSQL_ROOT_PASSWORD=secret

networks:
  web:

volumes:
  mysql:

上記では、phpコンテナとmysqlコンテナを設定しています。docker-compose upコマンドで、コンテナをまとめて起動できます。

$ docker-compose up                         
Creating network "sample_web" with the default driver
Creating volume "sample_mysql" with default driver
Creating sample_php_1   ... done
Creating sample_mysql_1 ... done
Attaching to sample_php_1, sample_mysql_1

docker-compose.yml解説

上記ymlファイルの解説です。

docker-compose.yml
version: '3.7'

Docker-compose.ymlの記述方法のバージョンを指定しています。古いバーションだと使用できないキーなどがあります。

docker-compose.yml
services:
  php:
    ...
  mysql:
    ...

Docker-composeはコンテナをサービスとして管理します。ここではphpとmysqlのコンテナ設定を書いていきます。

docker-compose.yml
services:
  php:
    image: my-php-apache:7.3.10
    ports:
      - 80:80
      - 443:443
    networks:
      - web
    volumes:
      - ./:/var/www
  • image - 使用するイメージ
  • ports - ポートのバインディング。ホスト:コンテナの順番でかく
  • networks - コンテナが所属するネットワーク(後述)
  • volumes - マウントするボリューム(後述)

ボリュームは前回利用しましたが、データをコンテナとは別に管理して永続化する仕組みでした。後ほどネットワークと合わせて詳しく解説します。

docker-compose.yml
  mysql:
    build: ./mysql
    expose:
      - 3306
    networks:
      - web
    volumes:
      - mysql:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=laravel
      - MYSQL_USER=laravel
      - MYSQL_PASSWORD=secret
      - MYSQL_ROOT_PASSWORD=secret
  • build - Dockerfileのパス

イメージではなくDockerfileも指定できます。

  • expose - 他のサービスに公開するポート(ホストからはアクセスできない)
  • environment - コンテナ内で使用できる環境変数

特定の環境変数を設定しておくと楽にカスタマイズできるように、ベースイメージ側で準備してくれていることがあります。
MySQLイメージのDockerHubページを見ると、MYSQL_DATABASEでデータベース名を設定できると分かります。
ここでは上からデータベース名、データベースのユーザー名、ユーザーのパスワード、ルートユーザーのパスワードを設定しています。Laravelからデータベースに接続するときに、この情報で接続します。

docker-compose.yml
networks:
  web:

volumes:
  mysql:

各サービスで使用するネットワーク・ボリュームを明示します。忘れると動作しません。

Dockerネットワーク

Docker-composeというよりDockerの仕組みですが、ここで説明します。

コンテナは同じ仮想ネットワークに配置されないと、相互にやりとりできません。なので、ApacheのコンテナとMySQLのコンテナは同じネットワークにしてあげます。

docker-compose.yml
services:
  php:
    networks:
      - web

  mysql:
    networks:
      - web

networks:
  web:

同じネットワークにすると、コンテナ内から他のコンテナにアクセスするときに、サービス名でアクセスできるようになります。
phpコンテナに配置したLaravelアプリケーションから、MySQLコンテナにアクセスするには.envファイルをこう書きます。

.env
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret

DB_HOSTがmysqlとサービス名になっていて、これで繋がります。内部的にはDockerがDNSサーバーを用意して、名前解決してくれるようです。

Dockerボリューム

こちらもDocker-composeというよりDockerの仕組みですが、ここで説明します。

MySQLコンテナを立ち上げて、DBにデータを保存してもコンテナを削除するとそのデータは消えてしまいます。データを永続保存するための仕組みがボリュームです。
ボリュームはコンテナとは切り離して管理されるもの、ということです。

ボリューム一覧は以下のコマンドで確認できます。

$ docker volume ls
DRIVER              VOLUME NAME
local               tmp
...

ボリュームには以下の3種類があります。

  • ホスト(host)
  • 匿名(anonymous)
  • 名前付き(named)

ホストボリューム

-v /host/path:/container/pathでホストのパスをコンテナのパスにマウントし、ボリュームとします。

$ docker container run -dit -v $HOME/tmp:/tmp alpine

上記の例ではコンテナを削除しても、/tmpの内容は$HOME/tmpに残ります。そしてホストボリュームはdocker volume lsには表示されません。

匿名ボリューム

-v /container/pathで、匿名ボリュームです。

$ docker container run -dit -v /tmp alpine

Dockerが隠蔽して管理するボリュームで、ハッシュ値がボリューム名となります。

$ docker volume ls
DRIVER              VOLUME NAME
local               a1138c254e5df7d419191d29e9e7e5acaf6389d69521c09eb00abe05889151ad

名前付きボリュームがなかった時代に使われていたようで、現在は名前付きボリュームを使います。
docker container run --rmで立ち上げたコンテナが削除される時、匿名ボリュームも削除されるという特徴があります。

名前付きボリューム

-v name:/container/pathで名前付きボリュームです。

$ docker container run -dit -v tmp:/tmp alpine

こちらもDockerが隠蔽して管理するボリュームですが、名前がありアクセスしやすいです。

$ docker volume ls
DRIVER              VOLUME NAME
local               tmp

またdocker container run --rmで立ち上げたコンテナが削除されても名前付きボリュームは削除されません。

どの種類のボリュームにするか

最初のdocker-compose.ymlを見ると、phpコンテナはホストでファイルを編集したいので、ホストボリューム./:/var/wwwにしています。
MySQLコンテナは、特にホストでMySQLが管理するファイルをいじることはないですから、名前付きボリュームmysql:/var/lib/mysqlです。ただホストボリューム./.db:/var/lib/mysqlなどにしても問題はないと思います。

また、前回でやったcomposerの例で考えてみましょう。
composer installするためのcomposer.jsonや、インストール結果のvendorディレクトリなどをコンテナとやりとりするだけであれば、ホストボリュームでいいです。
今後別のコンテナでcomposer installするためのキャッシュは、名前付きボリュームでどこからでも参照できるようにしておきましょう。

$ docker container run --rm -it -v $PWD:/app -v composer_cache:/tmp composer install

上記なら、2回目以降は以下のようにキャッシュからインストールしてくれます。

$ docker container run --rm -it -v $PWD:/app -v composer_cache:/tmp composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Package operations: 84 installs, 0 updates, 0 removals
  - Installing doctrine/inflector (v1.3.0): Loading from cache
  - Installing doctrine/lexer (1.1.0): Loading from cache
...

ボリュームにファイルがあると

  • ボリュームにファイルあり、コンテナになし
    ボリュームのファイルがコンテナにコピーされる

  • ボリュームにファイルなし、コンテナにあり
    コンテナのファイルがボリュームにコピーされる

  • 両方にファイルあり
    コンテナのファイルがなくなり、ボリュームのファイルがコンテナにコピーされる

実際にdocker-composeコマンドを使ってみよう

docker-compose.ymlがあれば、以下のコマンドでコンテナを一気に立ち上げることができます。

$ docker-compose up

起動したコンテナ、ネットワーク、ボリュームを確認してみましょう。

$ docker container ls                     
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                                      NAMES
af5e814670de        my-php-apache:7.3.10   "docker-php-entrypoi…"   5 minutes ago       Up 5 minutes        0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   sample_php_1
e4116e1a3bfa        sample_mysql           "docker-entrypoint.s…"   5 minutes ago       Up 5 minutes        3306/tcp, 33060/tcp                        sample_mysql_1

$ docker network ls  
NETWORK ID          NAME                DRIVER              SCOPE
81e33015a6f9        bridge              bridge              local
e2eab5ac1197        host                host                local
a8432cc388c6        none                null                local
37b562bfd9c1        sample_web          bridge              local

$ docker volume ls 
DRIVER              VOLUME NAME
local               sample_mysql

ネットワークのbridge,host,noneは、デフォルトで用意されているネットワークなので気にしないでください。

dockerコマンドと同じように、exec <service> <command>でコンテナ内でコマンドを実行できます。

$ docker-compose exec php bash
root@026c8f03e602:/var/www#

MySQLに接続するのであれば

$ docker-compose exec mysql mysql -u laravel -D laravel -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.

コンテナ削除もdocker-compose downでまとめてできます。コンテナとネットワークが削除され、ボリュームは残ります。

$ docker-compose down
Stopping sample_php_1   ... done
Stopping sample_mysql_1 ... done
Removing sample_php_1   ... done
Removing sample_mysql_1 ... done
Removing network sample_web

プロジェクト

コンテナ、ネットワーク、ボリューム名がsample_*になっています(コンテナ名はls結果の一番右に書いてあります)。これはsampleディレクトリ下でdocker-compose upしたからです。

他のディレクトリでdocker-compose upすると、そのディレクトリ名がプリフィックスになります。ディレクトリ名だとわかりにくい場合は、環境変数COMPOSE_PROJECT_NAMEや-pオプションでプリフィックスを指定できます。
docker-composeコマンドは.envファイルをみてくれるので、以下のようにしましょう。

.env
COMPOSE_PROJECT_NAME=プロジェクト名

プロジェクト固有のイメージ

docker image lsコマンドで所持イメージを確認してみます。

$ docker image ls                                                        
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
sample_mysql        latest              c8914e3551fe        35 seconds ago       385MB
my-php-apache       7.3.10              ed7f24566ef1        About a minute ago   452MB
php                 7.3.10-apache       b83ebda0d7d4        7 weeks ago          410MB
mysql               5.7.27              383867b75fd2        3 months ago         373MB

phpとmysqlはベースにした公式イメージで、my-php-apacheは前回自分でビルドしたイメージでした。
sample_mysqlは、docker-compose upした時にビルドされたイメージです。

docker-compose.ymlにbuildを指定すると、プロジェクトごとにイメージがビルドされます。今回は

docker-compose.yml
  mysql:
    build: ./mysql

としていたので、sampleがプリフィックスのプロジェクト固有のmysqlイメージになりました。

プロジェクトごとにイメージを持ちたいならbuildを、使いまわしたいなら最初にdocker buildしておき、それをimageに指定すれば良いでしょう。

終わりに

Docker-composeを使うことができれば、開発環境は構築できると思います。
他のイメージを使えば、PostgreSQLやRedisサーバー、メール送信のテストを行えるMailHogなどを立ち上げてLaravelで使用できるので試してみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-composeでexit status 1

はじめに

macOS Mojave 10.14.6
Docker 2.1.0.5

とりあえず

$ docker-compose up
...
front_nuxt  | npm ERR! Exit status 1
front_nuxt  | npm ERR! 
front_nuxt  | npm ERR! Failed at the Musclers@1.0.0 dev script.
front_nuxt  | npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
front_nuxt  | 
front_nuxt  | npm ERR! A complete log of this run can be found in:
front_nuxt  | npm ERR!     /root/.npm/_logs/2019-12-18T01_41_30_431Z-debug.log
front_nuxt  | front_nuxt exited with code 1

といってフロントくん(nuxt)が起動しない

$ docker-compose ps
   Name                 Command               State           Ports         
----------------------------------------------------------------------------
api_rails    bash -c rm -f 'tmp/pids/se ...   Up      0.0.0.0:8080->8080/tcp
db_psql      docker-entrypoint.sh postgres    Up      5432/tcp              
front_nuxt   docker-entrypoint.sh npm r ...   Exit 1      0.0.0.0:3000->3000/tcp

まぁ、だよね

原因

これがイマイチわからない。

やったこと

pruneでimageを一括削除した。

$ docker-compose down
$ docker system prune --volumes
# docker system pruneではvolumeまで削除してくれない
$ docker-compose up --build
# build忘れないようにね

で、buildが終わった後、無事起動しました。

最後に

最後にって書く必要あるのかわからないけどrailsとか使ってるなら、
db:createmigratedb:seed忘れないようにね。
DB空っぽだから何もできずに発狂した人もいるらしい←

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaTeXで年賀状を作る

はじめに

急に年賀状を用意しないといけない!という時どういったツールをつかうとよいだろうか?PowerPointなどのスライド作成ソフトを作ったり、あるいは専用のツールを購入するという手もあるかもしれない。この記事では、フリーソフトウェアである$\mathrm{\LaTeX}$を利用して美しい年賀状をつくる方法を紹介する。この方法は$\mathrm{\LaTeX}$とjletteraddressクラス、さらにそれをコンパイルする環境をDockerで作成したことによって、特に$\mathrm{\LaTeX}$の知識がなくとも勘で年賀状を出力できるのが利点である。また著者の知るかぎり年賀状を作るソフトウェアは数式やソースコードの出力に対応しているとは言えないだろうが、$\mathrm{\LaTeX}$では既存の$\mathrm{\LaTeX}$資産を利用できるため年賀状に数式やソースコードを容易に埋め込むことができるうえ、個人の論文や同人誌など他の$\mathrm{\LaTeX}$文書からデータを流用できるという利点もある。

上記のGitHubリポジトリにコードの一覧がある。この記事について疑問や改善点を見つけた場合は、気軽にコメントなどで教えてほしい。

成物果

このような年賀状を出力できる。

あとはこれを編集して印刷するだけ!

使い方

次のような手順で、すごく簡単に:point_up:のような年賀状を得ることができる。なおDockerdocker-composeが必要である。

  1. git clone git@github.com:y-yu/new-year-letter.git
  2. cd new-year-letter
  3. docker-compose up

いったんこれでletter.pdfを得ることができる。あとは好みにあわせてletter.texを書き換えていけばよい。

本文の編集

$\mathrm{\LaTeX}$ファイルの編集というとキツい印象があるかもしれないが、実はサンプルは非常にシンプルに書かれている。

letter.tex
\documentclass{jletteraddress}

\usepackage{amsmath}
\usepackage[papersize={100mm, 148mm}, margin=0mm]{geometry}

\newcommand{\bra}[1]{\mathinner{\left\langle{#1}\right|}}
\newcommand{\ket}[1]{\mathinner{\left|{#1}\right\rangle}}
\newcommand{\braket}[2]{\mathinner{\left\langle{#1}\middle|#2\right\rangle}}

\sendername{吉村 優}
\senderaddressa{茨城県つくば市天王台1-1-1}
\senderaddressb{筑波大学第三エリア3C212}
\senderpostcode{3058573}

\renewcommand{\baselinestretch}{1.2}

\begin{document}

\pagestyle{empty}

\addaddress
  {山田 太郎}{}
  {1138654}
  {東京都文京区本郷7-3-1}
  {}

\newpage
\newgeometry{
  top=10mm,
  left=10mm,
  right=10mm,
  bottom=5mm
}

\begin{abstract}
  \footnotesize
  この文章では、新年の挨拶を述べるための手紙である``年賀状''を作る方法について述べる。
  従来の年賀状を作成するソフトウェアは数式やソースコードの埋め込みが貧弱であったが、
  我々はそれを解決す方法として組版ソフト\LaTeX を利用する方法を示す。

  In this paper, we describe how to make a letter to hello new year.
  For conventional softwares that makes those letter,
  it's difficult to write mathmatical expressions or
  programming source code.
  We show how we solve that problem with \LaTeX.
\end{abstract}

\section*{Introduction}

年賀状は年の始めに互いに送信する手紙のことである。
近年はLINEなどに対抗するため、年賀状に次のような複雑な数式を埋め込みたい
ニーズが存在する。

{\scriptsize
\begin{align*}
  \left|\braket{+}{\varphi_{0 \oplus b, 0 \oplus b}}\right|^2 &= \left\{
\begin{array}{l}
\left|\braket{+}{\alpha\ket{0} + \beta\ket{1}}\right|^2 = \left|\frac{1}{\sqrt{2}}(\alpha + \beta)\right|^2 \\
\left|\braket{+}{\beta\ket{0} + \alpha\ket{1}}\right|^2 = \left|\frac{1}{\sqrt{2}}(\beta + \alpha)\right|^2
\end{array}
\right\} \\
&= \frac{(\alpha + \beta)^2}{2}
\end{align*}
}

\noindent
この文章ではこのような複雑な数式を埋め込むために\LaTeX を
利用した解決方法について述べる。

\begin{flushright}
  \tiny This letter was generated by \LaTeX.
\end{flushright}

\end{document}

下記にざっくりどういった意味なのか解説していく。

  • \sender○○○系のコマンドが差出人の情報を示す
  • \baselinestretchは行間をデフォルトの1.2倍へ広げている(これは好み)
  • \addaddressは宛先情報
  • \newgeometryで余白の設定

という感じで、あとは適当にabstractとかを消していじってみてdocker-compose upしてコンパイルしていけばOKである。これはこの記事の著者というよりもjletteraddressがすごいので、このように簡単になっている。

まとめ

このようにjletteraddressを使えば$\mathrm{\LaTeX}$で簡単に年賀状を作ることができる。ただ$\mathrm{\LaTeX}$のコンパイルは慣れていないと大変であるため、著者の貢献としてそこをDockerで隠蔽した。普段、論文や同人誌の執筆などで$\mathrm{\LaTeX}$を使う人がいれば、それと同じような感覚で使えるのは利点だと思う。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Desktop for Windows で Windows GUI コンテナを動かす #2

これは何?

Docker Desktop for Windows で Windows GUI コンテナを動かす の続きです。

結論

次の組み合わせ

  • WineHQ をコンテナで動かす
  • X410 をホストOS(windows) で動かす

Dockerfile

使った Dockerfile です.
32bit アプリを動かすつもりなら, コメントアウトしている ENV WINEARCH=win32 を有効にし, win32 環境を構築した方が良い

Dockerfile
FROM ubuntu:18.04

# @see https://qiita.com/fkshom/items/53de3a9b9278cd524099
RUN sed -i.bak -e "s%http://[^ ]\+%http://ftp.jaist.ac.jp/pub/Linux/ubuntu/%g" /etc/apt/sources.list

RUN set -eux; \
    dpkg --add-architecture i386 \
    && apt-get update \
    && apt-get install -y --no-install-recommends \
        software-properties-common \
        apt-transport-https \
        gpg-agent \
        curl \
    && curl -L -O https://dl.winehq.org/wine-builds/winehq.key \
    && apt-key add winehq.key \
    && apt-add-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ bionic main' \
    && rm winehq.key \
    && apt-get update \
    && apt-get install -y --install-recommends \
        winehq-stable \
    && apt-get install -y --no-install-recommends \
        winetricks \
    && apt-get remove --purge -y \
        software-properties-common \
    && apt-get autoremove -y \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

COPY local.conf /etc/fonts/local.conf

# for 32bit environment only
#ENV WINEARCH=win32

RUN set -eux; \
    apt-get update \
    && apt-get install -y --no-install-recommends \
        xvfb \
    && mkdir -p /root/.cache/wine \
    && cd /root/.cache/wine \
    && curl -L -O http://dl.winehq.org/wine/wine-gecko/2.47/wine_gecko-2.47-x86_64.msi \
    && curl -L -O http://dl.winehq.org/wine/wine-gecko/2.47/wine_gecko-2.47-x86.msi \
    && curl -L -O http://dl.winehq.org/wine/wine-mono/4.7.5/wine-mono-4.7.5.msi \
    && winetricks nocrashdialog win10 \
    && xvfb-run wineboot -u \
    && apt-get remove --purge -y \
        xvfb \
    && apt-get autoremove -y \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /root/.cache/wine/wine*.msi

local.conf は、 Connect to the container and create a '/etc/fonts/local.conf' file そのままです。 VcXsrv でも使えると思います。

local.conf
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
    <dir>/mnt/winfonts</dir>
</fontconfig>

wineboot は DISPLAY 環境変数が未設定だと途中で abort します. なので、 xvfb を使います.
また, 単に xvfb-run に GUI を捨てると wine-mono と wine_gecko の download を促す dialog が表示されてしまいます.
この問題を回避するため, あらかじめ wine-mono と wine_gecko の .msi を .cache/wine に配置しています. ここに .msi ファイルがあると, wineboot がキャッシュから mono と gecko を install してくれます.

現状の Dockerfile だと, winehq-stable のバージョンが変わったら, wget の download 先を書き換える必要があり, 面倒...

コンテナの起動

docker image を wine:latest で作成したとします.

X410 の場合

launch.cmd
setlocal
cd %~dp0
docker run -it --rm -v C:/Windows/Fonts:/mnt/winfonts:ro -v "%CD%:/root/workspace" -w /root/workspace -e DISPLAY=docker.for.win.localhost:0.0 wine %*

VcXsrv の場合

試していませんが、多分これでOKと思います
下記のバッチファイルから起動すると, VcXsrv の xhost + と docker run の DISPLAY を設定しに行きます.

launch.cmd
setlocal
cd %~dp0
set ARGS=%*
set DISPLAY=127.0.0.1:0.0
"%PROGRAMFILES%\VcXsrv\xhost" +

CHCP 437
for /f "tokens=3 usebackq" %%i in (`netsh interface ipv4 show address "vEthernet (Default Switch)"^|find "IP Address:"`)  do @set "IP=%%i"
docker run -it --rm -v C:/Windows/Fonts:/mnt/winfonts:ro -v "%CD%:/root/workspace" -e DISPLAY=%IP%:0.0 wine %ARGS%

Dockerfile で ENTRYPOINT を wine にしていないので bash が走るのがイマイチなので適当に直してください。

参考にしたところ

apt-getの利用リポジトリを日本サーバーに変更する
Installing WineHQ packages
X410
Launching Linux GUI apps from the Docker Console in Token2Shell (Store App)
How to change the codepage for DOS batch files to ANSI

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む