screwdriver.cdでci/cdパイプラインを作成

自動デプロイをしたい。 github actionsを使うとself-hosted runnerを使って自動デプロイができるようになる。 がself-hosted runnerはどうやらプライベートリポジトリにしか使えないみたいなんだよね。 ってことでプライベートリポジトリをscrewdriver.cdと連携して、自分のプロジェクトで使えるようにしたい。

導入方法

公式ホームページに書いてある通りに、

python <(curl -L https://tinyurl.com/sd-in-a-box)

を実行する。これをやると

This command will run a script that will create a Docker Compose file for you locally, complete with Oauth credentials using a generated JWT and a user-provided Oauth Client ID and secret. If you choose to do so, it will then Docker pull the Screwdriver API, UI, and log store images to bring up an entire Screwdriver instance locally for you to play with. All data written to a database will be stored in a data/ directory. For more configuration details, see our SD-in-a-box documentation.

らしい。

実際に実行すると

x@uvuntu:~$ python3 <(curl -L https://tinyurl.com/sd-in-a-box)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current   
                                 Dload  Upload   Total   Spent    Left  Speed     
100   674    0   674    0     0  14457      0 --:--:-- --:--:-- --:--:-- 14652    
100  9508  100  9508    0     0   112k      0 --:--:-- --:--:-- --:--:--  112k
🎁   Boxing up Screwdriver
👀   Checking prerequisites
🔐   Generating signing secrets
📤   Which SCM provider would you like to use? (github/gitlab/bitbucket) 

githubと入力して先に行く。 そうするとgithubとの連携をしてくれって頼まれる感じなので、連携をします。よろぴく。

 Please create a new OAuth application on GitHub.com
    Go to https://github.com/settings/applications/new to start the process       
    For 'Homepage URL' put http://100.64.1.52:9000
    For 'Authorization callback URL' put http://100.64.1.52:9001/v4/auth/login    

    When done, please provide the following values:

    Client ID:

指定されたリンクに飛ぶと Register a new OAuth applicationの画面が出てきます。 どこに何を入れればいいか、全部教えてくれるのでその通りに入力して最後にRegister applicationを入力します。(ちなみにgithubで、このページに行くには、setting-> 左のプルダウン一覧から一番下のdevelopper settings -> Oauth appからいける。)

clientIDが生成されるので、ターミナルでそのIDを入力します。 clientscretが必要なのでgihtubの方で生成します。 そこまで終わると、今度はこれが出てきます。

💾   Writing Docker Compose file
🚀   Screwdriver is ready to launch!

    Just run the following commands to get started!
      $ docker pull screwdrivercd/launcher:stable
      $ docker-compose pull
      $ docker-compose -p screwdriver up -d
      $ open http://100.64.1.52:9000

    Would you like to run them now? (y/n)

yを押すとこんな感じで続く

Would you like to run them now? (y/n) y
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/create?fromImage=screwdrivercd%2Flauncher&tag=stable": dial unix /var/run/docker.sock: connect: permission denied
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/screwdrivercd/store:stable/json": dial unix /var/run/docker.sock: connect: permission denied
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json?all=1&filters=%7B%22label%22%3A%7B%22com.docker.compose.config-hash%22%3Atrue%2C%22com.docker.compose.project%3Dscrewdriver%22%3Atrue%7D%7D": dial unix /var/run/docker.sock: connect: permission denied

👍   Launched!

    A few more things to note:
      - To stop/reset Screwdriver
        $ docker-compose -p screwdriver down
      - If your internal IP changes, update the docker-compose.yml and your SCM OAuth application
      - In-a-box does not support Webhooks including PullRequests for triggering builds
      - To create your own cluster, see https://docs.screwdriver.cd/cluster-management/kubernetes
      - For help with this and more, find us on Slack at https://slack.screwdriver.cd

❤️   Screwdriver Crew

でも、まあ書いてある通りで、失敗してるのよね。dockerはsudoで動かさないといけないから手動で起動する。 ちなみに、docker-composeの中身はこんな感じになっている。

version: '2'
services:
    api:
        image: screwdrivercd/screwdriver:stable
        ports:
            - 9001:80
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock:rw
            - ./data/:/tmp/sd-data/:rw
        environment:
            PORT: 80
            URI: http://100.64.1.52:9001
            ECOSYSTEM_UI: http://100.64.1.52:9000
            ECOSYSTEM_STORE: http://100.64.1.52:9002
            DATASTORE_PLUGIN: sequelize
            DATASTORE_SEQUELIZE_DIALECT: sqlite
            DATASTORE_SEQUELIZE_STORAGE: /tmp/sd-data/storage.db
            EXECUTOR_PLUGIN: docker
            EXECUTOR_QUEUE_ENABLED: "false"
            SECRET_WHITELIST: "[]"
            EXECUTOR_DOCKER_DOCKER: |
                {
                    "socketPath": "/var/run/docker.sock"
                }
            SCM_SETTINGS: |
                {
                    "github": {
                        "plugin": "github",
                        "config": {"username": "sd-buildbot", "email": "dev-null@screwdriver.cd", "secret": "SUPER-SECRET-SIGNING-THING", "oauthClientId": "Ov23liLCyey93WbZIqGp", "oauthClientSecret": "xxx"}
                    }
                }
            SECRET_JWT_PRIVATE_KEY: |
                -----BEGIN PRIVATE KEY-----
                hogehoge
                -----END PRIVATE KEY-----
            SECRET_JWT_PUBLIC_KEY: |
                -----BEGIN PUBLIC KEY-----
                hogehoge
                -----END PUBLIC KEY-----
    ui:
        image: screwdrivercd/ui:stable
        ports:
            - 9000:80
        environment:
            ECOSYSTEM_API: http://100.64.1.52:9001
            ECOSYSTEM_STORE: http://100.64.1.52:9002
            AVATAR_HOSTNAME: avatars*.githubusercontent.com

    store:
        image: screwdrivercd/store:stable
        ports:
            - 9002:80
        environment:
            ECOSYSTEM_UI: http://100.64.1.52:9000
            URI: http://100.64.1.52:9002
            SECRET_JWT_PUBLIC_KEY: |
                -----BEGIN PUBLIC KEY-----
                hogehoge
                -----END PUBLIC KEY-----

はいー、こんな感じで動いてるってことだね。全部で3つのコンテナが動くみたいだね。 api + ui + store。 では以下のコマンドで起動してみましょう。

sudo docker compose -p screwdriver up -d

完了しますた。では、満をじして、ローカルのUIにアクセスしてみましょう。 UIにアクセスするとgithubでの認証が必要だといわれる。ここで認証をして右上に自分のアカウント名が出ればオッケー。 しかし、やはりここで認証がうまくいく理由がちょっとわかってないかもしれないな。いやわかるか。向こうで発行したClient_IDとClient_secretが入ったURLに飛ぶ。認証しますか?って言われるので認証する。リダイレクトURLはこっちで指定したものになっているので、リダイレクトしてもらう。

昔自分が書いた記事で結構いいのがあったから乗っけておきますかね。 learn_oauth

大事なのは、どのフェーズにおいても、URLそのものに重要な情報を乗っけてやり取りしてるってこと。 まず、ユーザがログインページでgoogleによる認証をクリックしたとするよね。これはゲットリクエストなわけですわ。このgetリクエストに対して、サーバはユーザをgoogleの認証サーバにリダイレクトするわけです。 で、リダイレクトのurlの中にこのサイトの情報が乗ってるんですよね、サイトのIDってやつです。 このIDは事前にgoogleのdeveloperサイトでもらえます。google側はこのIDでどのサイトかを判別しているんですね。更にこのサイトでコールバックのURL (ユーザがgoogle側にこのアプリに情報を送ることを承認するボタンを押したあとにリダイレクトされるURL) も登録しておきます。 で、ユーザが承認すると、コールバックのURLにユーザの情報にアクセスするためのトークンを乗っけてこっちにリダイレクトされるわけですわ。 で、そのトークンを使って、ユーザの情報にアクセスできるようになるわけです。

screwdriverの概要

screwdriver.yamlの基本文法

以下の設定ファイルを見て、勉強してください

その1

jobs:
  hello:
    requires: ~commit
    image: image_of_docker_container
    steps:
      - echo: echo "Hello"

その2

実際にデプロイしてみる

screwdriverがgithubの変更を検知する方法

Screwdriver CI integrates with GitHub to detect changes in repositories primarily through webhooks. Here’s a breakdown of how it typically works and considerations for scenarios where the Screwdriver server might not be directly reachable from the internet:


How Webhooks Work: GitHub webhooks allow you to build or set up integrations which subscribe to certain events on GitHub.com. When one of those events is triggered, GitHub sends an HTTP POST payload to the webhook's configured URL.

Screwdriver Integration: When you set up a repository with Screwdriver, you typically configure a webhook on that repository to point to your Screwdriver server. This webhook is triggered by events such as push or pull request actions. Once triggered, GitHub sends information to the Screwdriver server about the event, which prompts Screwdriver to start the pipeline’s jobs.

2. Handling Networks Inaccessible from the Internet
If your Screwdriver server is running in a network that is not accessible from the internet (e.g., behind a corporate firewall or in a private network), GitHub cannot directly send webhook notifications to it. Here are a few strategies to handle this:

Proxy Server: Use a proxy server that can receive webhooks from GitHub and forward them to your internal Screwdriver server. This proxy would need to be exposed to the internet but can tightly control what data is forwarded internally.

VPN: Establish a VPN connection between your cloud presence (where GitHub can reach) and your internal network. This allows the GitHub webhook to reach your Screwdriver server through a secure channel.

という感じ。なので、やっぱりgithubからscrewdriverにプッシュ通知を送って走り出す。うん、実際にやってみたけど、だめだね。来ない。来させるためには、プロキシセントだめだ。nginxで設定を変更します。

もちろんgithubの方でも変更をしないといけないのですがね。 こっちの方の変更は、アプリの設定ー>webhooksからいける。 見ると、前回のリクエストがうまく処理されなかったことが書かれている。なるほどね。

はい、設定を変更したら、改めていきましょう。 コミットしてみますかね?

jobs:
  hello-world:
    image: alpine:latest
    steps:
      - greet: echo "Hello"
jobs:
  build:
    requires: [~commit,~pr]
    image: alpine:latest
    steps:
      - greet: echo "Hello"

testブランチを切って、そこにプッシュ、その後mainにマージするって作業をしました。 しかし、testにプッシュしただけじゃあ何も起こりませんね。それはそうですね。多分デフォルトではmainブランチへの変更のみを監視しているからね。

あー、どうやらパイプラインはブランチごとに作られているみたいなので、特定のブランチへのプッシュをトリガーにするとかは新たにパイプラインを作成する必要があるみたいですね。なるほどりかい。で、問題なく動いている感じです。

次の問題

githubからwebhookでscrewdriver serverにリクエストは届いているのだが、なぜかjobが始まらない。その原因を調べるため、screwdriverサーバを構成している3つのプロセスのコメントを見ることにした。 エラー内容は以下。

screwdriver-api-1    | {"level":"error","message":"pipeline:3 error in starting event Cannot read properties of undefined (reading 'getUploadCoverageCmd')","stack":"TypeError: Cannot read properties of undefined (reading 'getUploadCoverageCmd')\n    at CoverageBookend.getTeardownCommand (/usr/src/app/node_modules/screwdriver-coverage-bookend/index.js:56:36)\n    at /usr/src/app/node_modules/screwdriver-build-bookend/index.js:234:23\n    at Array.map (<anonymous>)\n    at Bookend.getTeardownCommands (/usr/src/app/node_modules/screwdriver-build-bookend/index.js:233:35)\n    at /usr/src/app/node_modules/screwdriver-models/lib/buildFactory.js:259:38\n    at async Promise.all (index 0)\n    at async Promise.allSettled (index 0)\n    at async startEvents (/usr/src/app/node_modules/screwdriver-api/plugins/webhooks/helper.js:383:21)\n    at async createEvents (/usr/src/app/node_modules/screwdriver-api/plugins/webhooks/helper.js:1045:20)\n    at async pushEvent (/usr/src/app/node_modules/screwdriver-api/plugins/webhooks/helper.js:1095:22)\n    at async handler (/usr/src/app/node_modules/screwdriver-api/plugins/webhooks/index.js:98:32)\n    at async exports.Manager.execute (/usr/src/app/node_modules/@hapi/hapi/lib/toolkit.js:60:28)\n    at async internals.handler (/usr/src/app/node_modules/@hapi/hapi/lib/handler.js:46:20)\n    at async exports.execute (/usr/src/app/node_modules/@hapi/hapi/lib/handler.js:31:20)\n    at async Request._lifecycle (/usr/src/app/node_modules/@hapi/hapi/lib/request.js:370:32)\n    at async Request._execute (/usr/src/app/node_modules/@hapi/hapi/lib/request.js:280:9)","timestamp":"2024-07-08T10:57:24.072Z"}
screwdriver-api-1    | {"level":"error","message":"[d7c42dae-3d18-11ef-81b0-dbea25b78196]: Error: Failed to start any events","timestamp":"2024-07-08T10:57:24.072Z"}    
screwdriver-api-1    | {"level":"error","message":"[d7c42dae-3d18-11ef-81b0-dbea25b78196]: Error: Failed to start any events","timestamp":"2024-07-08T10:57:24.072Z"}    
screwdriver-api-1    | 240708/105724.072, (1720436239126:a42dc7f97ab7:19:lycv7l7s:10028) [request,server,error] data: Error: Failed to start any events
screwdriver-api-1    |     at startEvents (/usr/src/app/node_modules/screwdriver-api/plugins/webhooks/helper.js:406:15)
screwdriver-api-1    |     at async createEvents (/usr/src/app/node_modules/screwdriver-api/plugins/webhooks/helper.js:1045:20)
screwdriver-api-1    |     at async pushEvent (/usr/src/app/node_modules/screwdriver-api/plugins/webhooks/helper.js:1095:22)
screwdriver-api-1    |     at async handler (/usr/src/app/node_modules/screwdriver-api/plugins/webhooks/index.js:98:32)
screwdriver-api-1    |     at async exports.Manager.execute (/usr/src/app/node_modules/@hapi/hapi/lib/toolkit.js:60:28)
screwdriver-api-1    |     at async internals.handler (/usr/src/app/node_modules/@hapi/hapi/lib/handler.js:46:20)
screwdriver-api-1    |     at async exports.execute (/usr/src/app/node_modules/@hapi/hapi/lib/handler.js:31:20)
screwdriver-api-1    |     at async Request._lifecycle (/usr/src/app/node_modules/@hapi/hapi/lib/request.js:370:32)
screwdriver-api-1    |     at async Request._execute (/usr/src/app/node_modules/@hapi/hapi/lib/request.js:280:9)
screwdriver-api-1    | 240708/105719.126, 

同じ問題を抱えている人がいる。 このissue これ完全にバグだね。マジでやめてほしい。。。

とりあえず、バグが出ているコンテナをバグ以前に戻してみる dockerhub これの screwdriver これがapiに対応する。2023/11月以前のであれば問題ないと考える。 バージョンでいうとこれかな。 screwdrivercd/screwdriver:v7.0.16 ってことでこれをdocker-composeに指定。 pull中。頼む、もうバグは嫌なんだ。

そしたら次のエラーが来ましたーー

"pipeline.configPipelineId" is required

ってことでlatesでやりなおしますーーー。たのむ、そろそろきてくれやーー。

はいlatestだとさっきと同じエラーが出るね。

Cannot read properties of undefined (reading 'getUploadCoverageCmd')

ってことでイライラしてきたので、自分でimageを作ることにしました。このメンテ名に頼ってたら一生来ないわ。はい、乙。

最後に、 screwdriver:v5.0.18 だけ試してみる。頼んだぜーーー

やっぱり駄目ですね。うん、ちょっと諦めます。もちろん自宅でできれば一番いいのですが、それを今やっている時間はないのでね。申し訳ないが会社の環境で実行させていただきますわ。

疑問点

self-hosted runnerはデプロイするサーバ上でrunnerが動いている感じだったけど、screwdriverはどういう感じなのだろうか??わからないなーー、 ansibleはコントロールプレーンからターゲットノードに対して変更をするときに走るから、まあ、職場ではそういう使い方をしているんだろう。デプロイノード数マジで馬鹿みたいに多いからね。