Menu

  • Home
  • Archives
  • Tags
  • RSS
March 11, 2013

Setting up a Haskell webserver to run in Google Compute Engine

Setting up a Haskell webserver to run in Google Compute Engine

I signed up for a project at the Google Cloud Console, and decided to try running a webserver on it, as a prerequisite to other future toy projects I might link to it.

First steps

Get ```gcutil``` and install it


$ sudo tar xvpzf gcutil-1.7.1.tar.gz  -C /usr/local/share

$ sudo ln -s /usr/local/share/gcutil-1.7.1/gcutil /usr/local/bin/gcutil

Then, link it up to your project


$ gcutil auth --project=agam-personal-project

It's a good idea to avoid entering the ```–project=``` bit every time, by saving your project name


$ gcutil getproject --project=agam-personal-project --cache_flag_values

+--------------------------+-------------------------------+

|         property         |             value             |

+--------------------------+-------------------------------+

| name                     | agam-personal-project                     |

| description              |                               |

| creation-time            | 2012-10-30T13:52:46.571-07:00 |

| ips                      | 108.59.85.232,173.255.125.245 |

|                          |                               |

| usage                    |                               |

|   instances              | 0.0/4.0                       |

|   cpus                   | 0.0/4.0                       |

|   ephemeral-addresses    | 0.0/4.0                       |

|   disks                  | 0.0/4.0                       |

|   disks-total-gb         | 0.0/250.0                     |

|   snapshots              | 0.0/20.0                      |

|   networks               | 1.0/1.0                       |

|   firewalls              | 2.0/10.0                      |

|   images                 | 0.0/4.0                       |

|                          |                               |

| common-instance-metadata |                               |

+--------------------------+-------------------------------+

By default, incoming HTTP is blocked. So enable it.


$ gcutil addfirewall http2 --description="Allow incoming http" --allowed="tcp:http"

INFO: Waiting for insert of firewall http2. Sleeping for 3s.

INFO: Waiting for insert of firewall http2. Sleeping for 3s.



Table of resources:



+-------+---------------------+------------------+------------+-------------+-------------+

| name  |     description     |     network      | source-ips | source-tags | target-tags |

+-------+---------------------+------------------+------------+-------------+-------------+

| http2 | Allow incoming http | networks/default | 0.0.0.0/0  |             |             |

+-------+---------------------+------------------+------------+-------------+-------------+



Table of operations:



+------------------------------------------------+------+--------+----------------+-----------------+-------------------------------+----------------+-------+---------+

|                      name                      | zone | status | status-message |     target      |          insert-time          | operation-type | error | warning |

+------------------------------------------------+------+--------+----------------+-----------------+-------------------------------+----------------+-------+---------+

| operation-1362984707558-4d7a09b531841-ea2eb01d |      | DONE   |                | firewalls/http2 | 2013-03-10T23:51:47.558-07:00 | insert         |       |         |

+------------------------------------------------+------+--------+----------------+-----------------+-------------------------------+----------------+-------+---------+

And now it's time to make choices!


gcutil addinstance basic-webserver-instance

1: europe-west1-a  (currently in maintenance)

2: europe-west1-b

3: us-central1-a

4: us-central1-b

5: us-central2-a

>>> 3

No particular reason, I picked ```us-central1-a``` (why no ```us-west``` ?). I wasn't sure which instance type to pick ... I went with the cheapest option with a disk (#2) for now.


1: n1-standard-1

2: n1-standard-1-d

3: n1-standard-2

4: n1-standard-2-d

5: n1-standard-4

6: n1-standard-4-d

7: n1-standard-8

8: n1-standard-8-d

9: n1-highcpu-2

10: n1-highcpu-2-d

11: n1-highcpu-4

12: n1-highcpu-4-d

13: n1-highcpu-8

14: n1-highcpu-8-d

15: n1-highmem-2

16: n1-highmem-2-d

17: n1-highmem-4

18: n1-highmem-4-d

19: n1-highmem-8

20: n1-highmem-8-d

>>> 2

10 choices of a host OS, I picked what seemed to be the latest ```gcel``` one.


1: projects/google/global/images/centos-6-v20121106

2: projects/google/global/images/centos-6-v20130104

3: projects/google/global/images/centos-6-v20130225

4: projects/google/global/images/gcel-10-04-v20121106

5: projects/google/global/images/gcel-10-04-v20130104

6: projects/google/global/images/gcel-10-04-v20130225

7: projects/google/global/images/gcel-12-04-v20121106

8: projects/google/global/images/gcel-12-04-v20130104

9: projects/google/global/images/gcel-12-04-v20130225

10: projects/google/global/images/centos-6-v20120912 (DEPRECATED)

>>> 9

If this is the first time running ```gcutil``` it will ask you to enter a passphrase for authentication, and then your instance should be inserted. As you can see below, this took about 35s for me.


INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.

INFO: Waiting for insert of instance basic-webserver-instance. Sleeping for 3s.



Table of resources:



+--------------------------+------------------------------+----------------------------------------------------+------------------+---------------+---------------+-------+---------------+---------+----------------+

|           name           |         machine-type         |                       image                        |     network      |  network-ip   |  external-ip  | disks |     zone      | status  | status-message |

+--------------------------+------------------------------+----------------------------------------------------+------------------+---------------+---------------+-------+---------------+---------+----------------+

| basic-webserver-instance | machineTypes/n1-standard-1-d | projects/google/global/images/gcel-12-04-v20130225 | networks/default | 10.240.31.254 | 108.59.80.144 |       | us-central1-a | RUNNING |                |

+--------------------------+------------------------------+----------------------------------------------------+------------------+---------------+---------------+-------+---------------+---------+----------------+



Table of operations:



+------------------------------------------------+---------------+--------+----------------+--------------------------------------------------+-------------------------------+----------------+-------+---------+

|                      name                      |     zone      | status | status-message |                      target                      |          insert-time          | operation-type | error | warning |

+------------------------------------------------+---------------+--------+----------------+--------------------------------------------------+-------------------------------+----------------+-------+---------+

| operation-1362986355529-4d7a0fd8d07a1-a21d7f92 | us-central1-a | DONE   |                | us-central1-a/instances/basic-webserver-instance | 2013-03-11T00:19:15.529-07:00 | insert         |       |         |

+------------------------------------------------+---------------+--------+----------------+--------------------------------------------------+-------------------------------+----------------+-------+---------+

You can then check if the instance matches what you wanted.


$ gcutil getinstance basic-webserver-instance

(output skipped)

Installing Stuff

Now that the instance is ready to go, you can ssh to it and get the stuff we need.


$ gcutil ssh basic-webserver-instance

...

... (skipping boilerplate)

... 

 * You appear to be running on an EPHEMERAL root disk.  Changes may be lost.

  For persistent data, use Persistent Disks:

  https://developers.google.com/compute/docs/disks#persistentdisks



agam@basic-webserver-instance:~$ sudo df -h

Filesystem      Size  Used Avail Use% Mounted on

/dev/sda1       9.6G 1019M  8.1G  12% /

none            1.9G  4.0K  1.9G   1% /dev

none            377M  140K  377M   1% /run

none            5.0M     0  5.0M   0% /run/lock

none            1.9G     0  1.9G   0% /run/shm

Yeah, the instance doesn't come with a persistent disk. So if you decide to delete the instance, your data is gone.

Eventually, I'll attach persistent disks and also use a startup script to allow me to quickly recreate VM state on new instances, but I'll punt on those for now.

Anyway, let's start installing stuff


$ sudo apt-get install haskell-platform

...

$ cabal update

...

$ cabal install yesod-platform

...

Haskell packages are still a bit flaky. For instance, I got this error and had to ignore it and pray nothing broke:


Warning: The following packages are likely to be broken by the reinstalls:

regex-posix-0.95.1

BTW this takes a long long time to complete, so go do something else for 5-10 minutes and then come back.

Running Yesod

For this example, I'm going to do a basic webserver, though the right thing to do in this case is to choose the option to configure the urls to be used with Google Cloud Storage


agam@basic-webserver-instance:~$ yesod init

Welcome to the Yesod scaffolder.

I'm going to be creating a skeleton Yesod project for you.



What do you want to call your project? We'll use this for the cabal name.



Project name: AgamsWeb

Yesod uses Persistent for its (you guessed it) persistence layer.

This tool will build in either SQLite or PostgreSQL or MongoDB support for you.

We recommend starting with SQLite: it has no dependencies.



    s      = sqlite

    p      = postgresql

    pf     = postgresql + Fay (experimental)

    mongo  = mongodb

    mysql  = MySQL

    simple = no database, no auth

    url    = Let me specify URL containing a site (advanced)



So, what'll it be? s

This sets up a basic scaffold with a few directories


agam@basic-webserver-instance:~/AgamsWeb$ ls -l

total 68

-rw-rw-r-- 1 agam agam 3907 Mar 11 08:18 AgamsWeb.cabal

-rw-rw-r-- 1 agam agam 2723 Mar 11 08:18 Application.hs

-rw-rw-r-- 1 agam agam 6583 Mar 11 08:18 Foundation.hs

drwxrwxr-x 2 agam agam 4096 Mar 11 08:18 Handler

-rw-rw-r-- 1 agam agam 1057 Mar 11 08:18 Import.hs

-rw-rw-r-- 1 agam agam  415 Mar 11 08:18 Model.hs

drwxrwxr-x 2 agam agam 4096 Mar 11 08:18 Settings

-rw-rw-r-- 1 agam agam 2736 Mar 11 08:18 Settings.hs

drwxrwxr-x 2 agam agam 4096 Mar 11 08:18 app

drwxrwxr-x 2 agam agam 4096 Mar 11 08:18 config

drwxrwxr-x 2 agam agam 4096 Mar 11 08:18 deploy

-rw-rw-r-- 1 agam agam  709 Mar 11 08:18 devel.hs

drwxrwxr-x 2 agam agam 4096 Mar 11 08:18 messages

drwxrwxr-x 4 agam agam 4096 Mar 11 08:18 static

drwxrwxr-x 2 agam agam 4096 Mar 11 08:18 templates

drwxrwxr-x 2 agam agam 4096 Mar 11 08:18 tests

Now to start this (empty) webserver


$ cabal install

$ yesod devel

This starts the server on ```localhost:3000```. Now the Yesod folks recommend running ```nginx``` as a reverse proxy in front, so let's do that (though it's obviously overkill for this example).


$ sudo apt-get install nginx

Create a basic reverse proxy config


events {

    worker_connections 4096;

}



http {

    server {

        listen 80;

        location / {

            proxy_pass http://127.0.0.1:3000/;

        }

    }

}

Then run nginx


sudo nginx -c nginx.conf

Hmm ... ok ... for whatever reason that didn't work. This seems like a fairly straightforward config. Screw it, run yesod directly on port 80.


sudo yesod devel --port=80

Now, if you open the associated IP in your browser, you should see the standard "Welcome to Yesod" placeholder !!


Tags: old-post

« Comparing `Hello World` in Go Comparing `Hello World` in C++ and Haskell »

Copyright © 2020 Agam Brahma

Powered by Cryogen