dersblog

Heroku

İdare edilen (managed) bulut ortamlarında Google'ın App Engine yaklaşımına benzer bir yaklaşım Heroku. Gerçi esnek bir sistem ayarlama şekli var, veri tabanı, Django, onun yerine Flask gibi yazılımları ortamımıza ekleyebiliyoruz.

Heroku sanal ortamının işlemci birimi dyno; bir dyno bir sanal Linux uygulama kabıdır (container). Ölçeklerken "dyno zamanından" bahsedilir, bir dyno bir saat kullanılmışsa bir dyno saati tüketilmiştir. Her uygulamaya Heroku tarafından ayda 750 bedava dyno saati verilir. Bu pek çok basit uygulama için yeterlidir. İsteyenler ek para ödeyerek daha fazla dyno saati satın alabilirler. Heroku bu şekilde para kazanmayı umuyor muhakkak.

Not: Dyno'lar 30 dakika kullanılmamışsa uykuya dalar, tekrar uyanırken biraz yavaşlık olabilir. Belki 10 dakikada bir uygulamayı dışarıdan "dürterek", ping yaparak uyanık tutmak düşünülebilir.

Üye olmak için

https://signup.heroku.com/signup/dc

Artık konsola girebiliriz. Heroku ile idare edilen uygulamalarımız, projelerimiz var, bu projeleri istediğimiz gibi ölçekleyebiliyoruz, onlara kaynak yöneltebiliyoruz. Mevcut uygulamaları görmek için

https://dashboard.heroku.com/apps

Mevcut bir app'i silmek için app ismine tıklanır, Settings kısmında en altta "Delete app" düğmesine basılıp app ismi bir daha girilip silme yapılır. Yeni app için Create New App seçin. isim global olarak özgün olmalı. Yerel bilgisayarda (daha önce kurulmadıysa bir kerelik)

sudo snap install heroku --classic

Simdi,

heroku login

dedikten sonra soruda enter'e basın, tarayıcıya gidiyor, bu geçici, iş bittikten sonra tarayıcı kapatılıp komut satırına dönülebilir. Biraz garip bir giriş yapma şekli ama işliyor.

Bir uygulama kuralım. Alttaki repo'da daha önce yaratılmış çok basit bir Heroku uygulaması var.

git clone git@github.com:franccesco/flask-heroku-example.git

cd flask-heroku-example

Ya da sıfırdan bir dizin içinde yeni kodlar koyup onları git init, ve git add ile bir repo'ya dahil edebilirdik. Sonuç aynı. Simdi,

heroku apps:create flask-heroku-example-[bir şeyler, özgün isim olsun diye]

ile Heroku projesi yaratılır. Bu noktada hala app sonuç ortamına gönderilmedi. Sayfanızı ziyaret ederseniz,

Heroku | Welcome to your new app!

mesajını görürsünüz.

git push heroku master

ile kod gönderin. Eğer problem çıkarsa .git/config içinde

url = https://git.heroku.com/flask-microblog-[...].git

olduğunu kontrol edin. Ve git push tekrarlayın.

Mimari açıdan bilinmesi gereken önemli bir faktör push yaptığımızda Heroku'nun servisleri tekrar başlattığı... Bu sırada birkaç işlem oluyor, repo'daki kod paketlenir, repo ile bağlı olan uygulama bulunup onun dyno'ları indirilip yeni kodla tekrar başlatılır. Bu önemli, çünkü mesela diyelim ki static altında sadece birkaç ufak sayfa değiştirdik, bunları göndersek push bunu anlayıp servisleri tekrar başlatmayabilirdi belki diye düşünebilirdik. Bu doğru değil. Bu tekrar başlatma sırasında kullanıcı sisteme ufak bir süre erisemeyebilir, bu sebeple, mesela bir içerik idare sistemi yazıyorsak bunu repo üzerinden yapmamak gerekir.

heroku open

ile tarayıcıyı direk uygulamanın işlediği URL'i ziyared edecek şekilde açabiliyoruz.

Heroku'nun çok Github repo merkezli işlediğini farketmişizdir herhalde. Kod gönderirken repo dizini içinde olmak lazım, heroku open deyince o repo ile alakalı olan URL biliniyor, vs. Yani Heroku uygulaması ile repo birbiri ile alakalı hale geliyor. Sistemleri böyle işliyor.

Ölçekleme

heroku ps:scale web=1
Scaling dynos... done, now running web at 1:Free

web=2 diyebilirdik.

Ayarlar

Üstte gösterilen örnek Python bazlı projeydi. Bu tür projelerin (ve genel Heroku projelerinin) ayarı için Pipfile ve Procfile dosyaları var. Bu dosyalar Heroku proje dizininde en üstte görülebiliyor. Dosya runtime.txt içinde hangi python versiyonu istediğimiz seçilebilir.

Dosya Sistemi

Dikkat: idare edilen bir ortam olduğu için Heroku "disk" ve "dosya sistemi" erişimini garanti etmiyor. Daha doğrusu bir dosya sistemi var ama bu sistem birdenbire değişip bir başka makinanın sistemine dönüşebiliyor (herhalde arka planda yük dağıtımı yaparken kod bir o bir makinaya kaydırılıyor). Bu yüzden Heroku disk sistemi için "uçucu (ephemeral)" deniyor. Diske yazılan bir şeyin orada kalacağına güvenmemek lazım. Kalıcı olmasını istediğimiz şeyleri Heroku tarafından desteklenen Posgresql tabanına yazmak lazım.

Log

Geliştirme makinanızda heroku logs --tail ile nihai ortamdaki print ve benzeri komutlarının çıktısını takip edebilirsiniz.

Python Paket Kullanımı

Eğer servis içinde python'un kullanması gereken ek paketler varsa, bunları Pipfile içine koyabiliriz, mesela ek iki paket olarak

...
requests = "*"
urllib3 = "*"

ekleyebilirdim. Eğer versiyon numarasını da vermek istersem, mesela

bcrypt = ">=1.1"
cryptography = "==2.3"

kullanımı işliyor. Bu arada eğer varsa Pipfile.lock dosyasını silin. Eğer problem çıkarsa uygulamanızı silip tekrar yaratın. Not: İnternet'te requirements.txt kullanımı ile ilgili bazı tavsiyeler var ama bunlar işlemiyor.

Uygulamamızın kaç bedava saati kaldığını görmek için

heroku ps -a [uygulama ismi]

Örnek sonuç

Free dyno hours quota remaining this month: 546h 5m (99%)
Free dyno usage for this app: 0h 55m (0%)
For more information on dyno sleeping and how to upgrade, see:
https://devcenter.heroku.com/articles/dyno-sleeping

=== web (Free): gunicorn app:app (1)
web.1: up 2019/05/27 12:19:55 +0300 (~ -172s ago)

Her hesaba her ay 550 bedava dyno zamanı veriliyor. Eğer kredi kartı verilirse (bir ücret kesileceğinden değil, kayıtlarda bulunması amacıyla) 450 saat aylık daha ekleniyor. Sadece 550'yi baz alalım, bu günlük 550 / 30 = 18 kusur saat demektir, bir dyno bir saat işliyorsa (nasıl işlerse işlesin, çok iş, az iş yapsın aynı) zaman buradan düşülecektir. Dyno 30 dakika kullanılmadığında uykuya daldığını düşünürsek, günlük 24 saatinin 18'inde hazır olabilecek bir servis fena sayılmaz. Eğer iki dyno başlatırsak 550 saat ikisine eşit bölüştürülür herhalde.

https://devcenter.heroku.com/articles/free-dyno-hours

Komut Satırı

Repo içinde heroku run python ile bir Python komut satırı, yorumlayıcı elde edebiliriz. Tabii aslında normal dizüstünde çalıştığımızda python ya da ipython işletince olanlardan ufak bir farkı var, yeni yorumlayıcı başlatmıyoruz, Heroku servisimize bir anlamda "bağlanıyoruz". Bu durumda, ve Flask çerçevesinde, mesela app.py içinde bir Flask uygulamamız varsa, import app ile bu uygulamanın koduna erisebiliriz.

Veri Tabani Eklemek

Uygulamamıza bir servis olarak bir Postgresql tabanı "ekleyebiliriz".

https://data.heroku.com/

gidiyoruz, seçeneklerden Heroku Postgres seçiyoruz, "Create one" tıklıyoruz, sonraki ekranda "Install Heroku Postgres"'e tıklıyoruz. Sonraki pencerede PG'nin hangi uygulamaya atanacağı (app to provision to) soruluyor, bu kutuya istediğimiz uygulamanın ismini yazıyoruz. Seçip "Provision add-on" diyoruz. Belli PG seviyeleri var, bedava en az kapsamlı olan "Hobby Dev" seviyesi. Olağan değer bu olacaktır zaten.

PG eklendikten sonra tabana tıklarız, çıkan ekranda Settings ve View Credentials ile tabana erişmek için gereken makina, taban ismi, kullanıcı, vs bilgileri görebiliriz. Fakat bu bilgileri alıp bir kenara yazmak, sonra uygulamanıza dışarıdan (bir ayar dosyası, ya da python kodu içinde) eklemek yerine, taban URL'ini sistem çevre değişkeni os.environ['DATABASE_URL'] ile almak en iyisi. Bu bilgi oraya Heroku sistemi tarafından set ediliyor. Bu şekilde okumak bir diğer açıdan daha önemli, Heroku bazen güvenlik açısından taban erişim bilgilerini rasgele bir şekilde değiştirebiliyor, sonra servisimizi tekrar başlatıyor. Bu durumda Heroku yeni taban bilgisini çevre değişkenine atar, ama koda, ayar dosyasına yazmışsak, değişimleri otomatik olarak uygulayamayız.

Flask, SQLAlchemy Kullanım Kalıbı

SQLAlchemy bir ORM paketi, class tanımları ile SQL tabana erişmemizi sağlıyor, hatta tabloları sıfırdan yaratmamızı sağlıyor. Bu paket için bir kullanım kalıbı şöyle olabilir, Flask app.py içinde, ana script seviyesinde

class ConfigClass(object):
    ...
    SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    ..

app = Flask(__name__)

app.config.from_object(__name__+'.ConfigClass')

db = SQLAlchemy(app)

class User(db.Model, UserMixin):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    active = db.Column('is_active', db.Boolean(), nullable=False, server_default='1')
    ...

def create_all():
    db.create_all()
    if not User.query.filter(...).first():
        user = User(
            ...
        )
        db.session.commit()

Burada db.create_all() ile taban yaratmak, gerekli bazı başlangıç verilerini eklemek için create_all() metotunu yazdık. Bazıları bu metotu ana script seviyesine koyabilir, böylece her script yüklendiğinde bu çağrı yapılmış olur. Ama db.create_all() metotu taban mevcut ise tablo yaratmayı tekrarlamaz, veri eklemeyi de mevcut veriye bakacak şekilde yazabiliriz, vs. Fakat eğer tek bir kez elle kendimiz bu işi tetiklemek istiyorsak (benim tercihim), heroku run python ile komut satırına girip import app ve app.create_all() ile çağrıyı bir kerelik kendimiz yapabiliriz.

Komut satırında SQL sorguları elle yapabiliriz bu arada,

>>> res = app.db.engine.execute('select * from users');
>>> print (list(res))

gibi..

[1] https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xviii-deployment-on-heroku

[2] https://devcenter.heroku.com/articles/getting-started-with-python

[3] https://realpython.com/flask-by-example-part-2-postgres-sqlalchemy-and-alembic/

[4] http://blog.sahildiwan.com/posts/flask-and-postgresql-app-deployed-on-heroku/


Yukarı