-
Notifications
You must be signed in to change notification settings - Fork 0
/
azure-i-love-you-i-love-you-not.html
145 lines (124 loc) · 10.9 KB
/
azure-i-love-you-i-love-you-not.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8">
<link rel="dns-prefetch" href="//ajax.googleapis.com">
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="dns-prefetch" href="//syndication.twitter.com">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Azure I love you, I love you not</title>
<meta name="author" content="Rob Grant">
<meta name="description" content="">
<!-- http://t.co/dKP3o1e -->
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
<link rel="icon" href="/theme/img/favicon.ico">
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
<meta name="msapplication-TileColor" content="#e8780c">
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
<meta name="theme-color" content="#ffffff">
<link href='http://fonts.googleapis.com/css?family=Oswald:300%7CKhula%7CFenix%7CRoboto+Condensed:300' rel='stylesheet' type='text/css' >
<link property='stylesheet' type="text/css" rel="stylesheet" href="theme/css/normalize.css">
<link property='stylesheet' type="text/css" rel="stylesheet" href="theme/css/main.css">
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<div id="orangeback">
</div>
<header>
<div id="header-heading">
<h1 class="heading"><a href="/"><img src="theme/img/logo-white-blinking.gif" alt="Rob Online" /></a></h1>
</div>
</header>
<div id="whiteback">
<main>
<div id="lefty">
</div>
<div id="articles">
<article itemscope itemtype="http://schema.org/BlogPosting">
<header>
<h1><a href="/azure-i-love-you-i-love-you-not.html">Azure I love you, I love you not</a></h1>
<span class="byline">by Rob, from <time class="timeago" itemprop="datePublished" datetime="2019-12-29T21:21:00+00:00">Sun 29 December 2019</time></span>
</header>
<div class="article-content">
<p>One of our services, a URL shortener, is written in Python and hosted in Kubernetes. Given it's a tiny service, just two endpoints, I wanted to try moving to Serverless.</p>
<p>Azure Functions were my tools of choice. Start to finish it was probably 6 hours of my time, including learning some basic Terraform (and the Azure provider). Here's how it went.</p>
<h2>The functions</h2>
<p>I wanted to host it as cheaply as possible, if not free. The service is low-traffic, and I fancied writing it in Go instead of Python, for a change. That last one was instantly blown out of the water - Azure Functions support C#, Javascript, something (I forget, but it's not Go; it's probably Java) and Python. Fine. I'll stick with Python. The documentation looks really good, and I walk through an initial tutorial with no problems. The <code>func</code> command is pretty neat, and the locally running function is very fast to start and respond, as one would hope.</p>
<p>I have to rewrite a bit of code. The old service was written in Flask, with Flask-SQLAlchemy. While it's possible to host totally custom code in an Azure Function, as a Docker image, I wanted the purest, and cheapest, Azure Function experience. So no Flask, and vanilla SQLAlchemy. The documentation was confusing here - it recommended an odd folder naming convention (e.g. <a href="https://github.com/MicrosoftDocs/azure-docs/issues/45009">here</a> and <a href="https://github.com/MicrosoftDocs/azure-docs/issues/45036">here</a>), and <a href="https://github.com/MicrosoftDocs/azure-docs/issues/43755">skipped over some useful points</a>. Importing shared code is done via relative folder imports. Installing dependencies can only be done with a requirements.txt file - no nice <a href="https://python-poetry.org/">Poetry</a> packaging here!</p>
<p>While a lot of effort's been put in, some of the inconsistencies indicate the Python side of things hasn't had the chance to benefit from much product management. But despite all that, the tech's good and it works locally. Cool!</p>
<h2>The infrastructure</h2>
<p>The documentation here is good, and covers using Powershell (barf) and the Azure commandline tool <code>az</code>. The latter is pretty good, and following the basic tutorial to get things up and running is good - I was making infrastructure very fast.</p>
<p>Then I found that Azure has good <a href="https://www.terraform.io">Terraform</a> support. Diving into Terraform a little (there's a good Pluralsight course, and the docs are pretty great), I rewrote what I'd done in that format. Much better.</p>
<p>By this point, I had the following infrastructure:</p>
<ul>
<li>Resource group</li>
<li>Storage (function apps all need storage)</li>
<li>App service (they need one of these as well)</li>
<li>Function app</li>
<li>Key Vault</li>
<li>App Insights (this is how you see logs in an Azure Function)</li>
<li>API Management (Azure's API Gateway)</li>
</ul>
<p>It's hard to overstate how magical this declarative syntax is the first time you use it. It's not perfect, but then I think I tried to do too much with it. Creating a user in Postgres and inserting their credentials into the Key Vault, and retrieving them in the function app as environment variables may have been a step too far.</p>
<p><em>But</em>, that last bit is pretty easy and can all be done in Terraform. It's just not particularly well documented.</p>
<p>First, create a managed service identity for your function app - this is pretty great. It creates a service principal in Azure's Active Directory without you having to think of an email address for a service. Then allow that principal to log into the Key Vault, with <code>get</code> permissions. Make sure your Terraform user has <code>set</code> permissions, and has set in the relevant secrets. Your Key Vault needs to have firewall permissions opened up - it can't just trust Azure services, for some reason. Finally, add into the environment variable settings of the function app this odd syntax for the variable value, which will look inside Key Vault for the actual value: <code>@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.my_key_vault_secret.id})</code>. The ${} variable is a Terraform one - it outputs the full URI to the secret.</p>
<p>Pretty good, right? It'd be even better if the app could automatically log into Postgres, but that would require code changes in the app as well, making it less portable, or moving to C# and Entity Framework, which I can't be bothered to do. So I'm happy.</p>
<p>I haven't enabled the API Gateway yet - when I do, I'll lock down the network and add in rate limiting to make it hard to guess shortened URLs.</p>
<h2>Publishing the app to Azure</h2>
<p>So I have an app that runs locally, and some infrastructure waiting to do useful things. All I have to do now is publish the app, and see it all working!</p>
<p>Of course, it's not that easy. Turns out, for some reason (<a href="https://github.com/Azure/azure-functions-python-worker/issues/598">at time of writing</a>), the function won't build with SQLAlchemy as a dependency. It's possible this can work in a different setup; for example, with a function app that is always on in a Docker image, rather than a function that only fires up on use, but I'd really like to implement the latter.</p>
<p>My journey stops, or at least pauses, here. Other than the showstopper, I really like what I'm seeing, even though it's a little rough around the edges.</p>
<p>Thanks for reading. I hope anyone who's trying to do something similar got something useful out of it, even if that someone is just future me!</p>
</div>
</article>
</div>
</main>
<footer>
<a href="#" class="totop">↑</a>
<p>Copyright Robert Grant 2015-2020</p>
<p>
<a href="https://stackoverflow.com/users/61938/robert-grant"><img src="theme/img/stackoverflow-logo-white-32x32.png" alt="Me on Stack Overflow" /></a>
<a href="https://github.com/robertlagrant"><img src="theme/img/GitHub-Mark-Light-64px.png" style="height: 36px" alt="Me on Github" /></a>
<a href="http://twitter.com/robertlagrant"><img src="theme/img/twitter-logo-white-32x32.png" alt="Me on Twitter" /></a>
<a href=""><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="40px" height="40px" id="RSSicon" viewBox="0 -32 256 256">
<circle cx="68" cy="189" r="24" fill="#FFF"/>
<path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" fill="#FFF"/>
<path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" fill="#FFF"/>
</svg></a>
</p>
</footer>
</div>
<script src="theme/js/vendor/modernizr-2.8.3.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="theme/js/vendor/jquery-1.11.2.min.js"><\/script>')</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.1/jquery.timeago.min.js"></script>
<script>window.jQuery || document.write('<script src="theme/js/vendor/jquery.timeago-1.4.1.min.js"><\/script>')</script>
<script src="theme/js/plugins.js"></script>
<script src="theme/js/main.js"></script>
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
<!--<script>
(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=
function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;
e=o.createElement(i);r=o.getElementsByTagName(i)[0];
e.src='//www.google-analytics.com/analytics.js';
r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));
ga('create','UA-XXXXX-X','auto');ga('send','pageview');
</script>-->
</body>
</html>