index.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. import * as awsx from '@pulumi/awsx'
  2. import * as eks from '@pulumi/eks'
  3. import * as docker from '@pulumi/docker'
  4. import * as pulumi from '@pulumi/pulumi'
  5. import { configMapFromFile } from './configMap'
  6. import * as k8s from '@pulumi/kubernetes'
  7. import * as s3Helpers from './s3Helpers'
  8. import { CaddyServiceDeployment, PostgresServiceDeployment } from 'pulumi-common'
  9. require('dotenv').config()
  10. const config = new pulumi.Config()
  11. const awsConfig = new pulumi.Config('aws')
  12. const isMinikube = config.getBoolean('isMinikube')
  13. const externalIndexerUrl = config.get('externalIndexerUrl')
  14. const skipProcessor = config.getBoolean('skipProcessor')
  15. export let kubeconfig: pulumi.Output<any>
  16. export let joystreamAppsImage: pulumi.Output<string>
  17. let provider: k8s.Provider
  18. if (skipProcessor && externalIndexerUrl) {
  19. pulumi.log.error('Need to deploy atleast one component, Indexer or Processor')
  20. throw new Error(`Please check the config settings for skipProcessor and externalIndexerUrl`)
  21. }
  22. if (isMinikube) {
  23. provider = new k8s.Provider('local', {})
  24. // Create image from local app
  25. joystreamAppsImage = new docker.Image('joystream/apps', {
  26. build: {
  27. context: '../../../',
  28. dockerfile: '../../../apps.Dockerfile',
  29. },
  30. imageName: 'joystream/apps:latest',
  31. skipPush: true,
  32. }).baseImageName
  33. // Uncomment the below line if you want to use a pre built image
  34. // joystreamAppsImage = pulumi.interpolate`joystream/apps`
  35. } else {
  36. // Create a VPC for our cluster.
  37. const vpc = new awsx.ec2.Vpc('query-node-vpc', { numberOfAvailabilityZones: 2, numberOfNatGateways: 1 })
  38. // Create an EKS cluster with the default configuration.
  39. const cluster = new eks.Cluster('eksctl-query-node', {
  40. vpcId: vpc.id,
  41. subnetIds: vpc.publicSubnetIds,
  42. desiredCapacity: 3,
  43. maxSize: 3,
  44. instanceType: 't2.large',
  45. providerCredentialOpts: {
  46. profileName: awsConfig.get('profile'),
  47. },
  48. })
  49. provider = cluster.provider
  50. // Export the cluster's kubeconfig.
  51. kubeconfig = cluster.kubeconfig
  52. // Create a repository
  53. const repo = new awsx.ecr.Repository('joystream/apps')
  54. joystreamAppsImage = repo.buildAndPushImage({
  55. dockerfile: '../../../apps.Dockerfile',
  56. context: '../../../',
  57. })
  58. // Uncomment the below line if you want to use a pre built image
  59. // joystreamAppsImage = pulumi.interpolate`joystream/apps`
  60. }
  61. const resourceOptions = { provider: provider }
  62. const name = 'query-node'
  63. // Create a Kubernetes Namespace
  64. const ns = new k8s.core.v1.Namespace(name, {}, resourceOptions)
  65. // Export the Namespace name
  66. export const namespaceName = ns.metadata.name
  67. let appLabels = { appClass: name }
  68. const defsConfig = new configMapFromFile(
  69. 'defs-config',
  70. {
  71. filePath: '../../../types/augment/all/defs.json',
  72. namespaceName: namespaceName,
  73. },
  74. resourceOptions
  75. ).configName
  76. if (!externalIndexerUrl) {
  77. const indexerDbName = 'indexer-db'
  78. const indexerDb = new PostgresServiceDeployment(
  79. indexerDbName,
  80. {
  81. namespaceName: namespaceName,
  82. env: [
  83. { name: 'POSTGRES_USER', value: process.env.DB_USER! },
  84. { name: 'POSTGRES_PASSWORD', value: process.env.DB_PASS! },
  85. { name: 'POSTGRES_DB', value: process.env.INDEXER_DB_NAME! },
  86. ],
  87. storage: 10,
  88. },
  89. resourceOptions
  90. )
  91. const indexerMigrationJob = new k8s.batch.v1.Job(
  92. 'db-migration',
  93. {
  94. metadata: {
  95. namespace: namespaceName,
  96. },
  97. spec: {
  98. backoffLimit: 0,
  99. template: {
  100. spec: {
  101. containers: [
  102. {
  103. name: 'db-migration',
  104. image: joystreamAppsImage,
  105. imagePullPolicy: 'IfNotPresent',
  106. resources: { requests: { cpu: '100m', memory: '100Mi' } },
  107. env: [
  108. {
  109. name: 'WARTHOG_DB_HOST',
  110. value: indexerDbName,
  111. },
  112. {
  113. name: 'DB_HOST',
  114. value: indexerDbName,
  115. },
  116. { name: 'WARTHOG_DB_DATABASE', value: process.env.INDEXER_DB_NAME! },
  117. { name: 'DB_NAME', value: process.env.INDEXER_DB_NAME! },
  118. { name: 'DB_PASS', value: process.env.DB_PASS! },
  119. ],
  120. command: ['/bin/sh', '-c'],
  121. args: ['yarn workspace query-node-root db:prepare; yarn workspace query-node-root db:migrate'],
  122. },
  123. ],
  124. restartPolicy: 'Never',
  125. },
  126. },
  127. },
  128. },
  129. { ...resourceOptions, dependsOn: indexerDb.service }
  130. )
  131. appLabels = { appClass: 'indexer' }
  132. const indexerDeployment = new k8s.apps.v1.Deployment(
  133. 'indexer',
  134. {
  135. metadata: {
  136. namespace: namespaceName,
  137. labels: appLabels,
  138. },
  139. spec: {
  140. replicas: 1,
  141. selector: { matchLabels: appLabels },
  142. template: {
  143. metadata: {
  144. labels: appLabels,
  145. },
  146. spec: {
  147. containers: [
  148. {
  149. name: 'redis',
  150. image: 'redis:6.0-alpine',
  151. ports: [{ containerPort: 6379 }],
  152. },
  153. {
  154. name: 'indexer',
  155. image: 'joystream/hydra-indexer:3.0.0',
  156. env: [
  157. { name: 'DB_HOST', value: indexerDbName },
  158. { name: 'DB_NAME', value: process.env.INDEXER_DB_NAME! },
  159. { name: 'DB_PASS', value: process.env.DB_PASS! },
  160. { name: 'DB_USER', value: process.env.DB_USER! },
  161. { name: 'DB_PORT', value: process.env.DB_PORT! },
  162. { name: 'INDEXER_WORKERS', value: '5' },
  163. { name: 'REDIS_URI', value: 'redis://localhost:6379/0' },
  164. { name: 'DEBUG', value: 'index-builder:*' },
  165. { name: 'WS_PROVIDER_ENDPOINT_URI', value: process.env.WS_PROVIDER_ENDPOINT_URI! },
  166. { name: 'TYPES_JSON', value: 'types.json' },
  167. { name: 'PGUSER', value: process.env.DB_USER! },
  168. { name: 'BLOCK_HEIGHT', value: process.env.BLOCK_HEIGHT! },
  169. ],
  170. volumeMounts: [
  171. {
  172. mountPath: '/home/hydra/packages/hydra-indexer/types.json',
  173. name: 'indexer-volume',
  174. subPath: 'fileData',
  175. },
  176. ],
  177. command: ['/bin/sh', '-c'],
  178. args: ['yarn db:bootstrap && yarn start:prod'],
  179. },
  180. {
  181. name: 'hydra-indexer-gateway',
  182. image: 'joystream/hydra-indexer-gateway:3.0.0',
  183. env: [
  184. { name: 'WARTHOG_STARTER_DB_DATABASE', value: process.env.INDEXER_DB_NAME! },
  185. { name: 'WARTHOG_STARTER_DB_HOST', value: indexerDbName },
  186. { name: 'WARTHOG_STARTER_DB_PASSWORD', value: process.env.DB_PASS! },
  187. { name: 'WARTHOG_STARTER_DB_PORT', value: process.env.DB_PORT! },
  188. { name: 'WARTHOG_STARTER_DB_USERNAME', value: process.env.DB_USER! },
  189. { name: 'WARTHOG_STARTER_REDIS_URI', value: 'redis://localhost:6379/0' },
  190. { name: 'WARTHOG_APP_PORT', value: process.env.WARTHOG_APP_PORT! },
  191. { name: 'PORT', value: process.env.WARTHOG_APP_PORT! },
  192. { name: 'DEBUG', value: '*' },
  193. ],
  194. ports: [{ name: 'hydra-port', containerPort: Number(process.env.WARTHOG_APP_PORT!) }],
  195. },
  196. ],
  197. volumes: [
  198. {
  199. name: 'indexer-volume',
  200. configMap: {
  201. name: defsConfig,
  202. },
  203. },
  204. ],
  205. },
  206. },
  207. },
  208. },
  209. { ...resourceOptions, dependsOn: indexerMigrationJob }
  210. )
  211. // Create a Service for the Indexer
  212. const indexerService = new k8s.core.v1.Service(
  213. 'indexer',
  214. {
  215. metadata: {
  216. labels: appLabels,
  217. namespace: namespaceName,
  218. name: 'indexer',
  219. },
  220. spec: {
  221. ports: [{ name: 'port-1', port: 4000, targetPort: 'hydra-port' }],
  222. selector: appLabels,
  223. },
  224. },
  225. resourceOptions
  226. )
  227. }
  228. if (!skipProcessor) {
  229. const processorDbName = 'processor-db'
  230. const processorDb = new PostgresServiceDeployment(
  231. processorDbName,
  232. {
  233. namespaceName: namespaceName,
  234. env: [
  235. { name: 'POSTGRES_USER', value: process.env.DB_USER! },
  236. { name: 'POSTGRES_PASSWORD', value: process.env.DB_PASS! },
  237. { name: 'POSTGRES_DB', value: process.env.DB_NAME! },
  238. ],
  239. storage: 10,
  240. },
  241. resourceOptions
  242. )
  243. const processorMigrationJob = new k8s.batch.v1.Job(
  244. 'processor-db-migration',
  245. {
  246. metadata: {
  247. namespace: namespaceName,
  248. },
  249. spec: {
  250. backoffLimit: 0,
  251. template: {
  252. spec: {
  253. containers: [
  254. {
  255. name: 'db-migration',
  256. image: joystreamAppsImage,
  257. imagePullPolicy: 'IfNotPresent',
  258. resources: { requests: { cpu: '100m', memory: '100Mi' } },
  259. env: [
  260. {
  261. name: 'WARTHOG_DB_HOST',
  262. value: processorDbName,
  263. },
  264. {
  265. name: 'DB_HOST',
  266. value: processorDbName,
  267. },
  268. { name: 'WARTHOG_DB_DATABASE', value: process.env.DB_NAME! },
  269. { name: 'DB_NAME', value: process.env.DB_NAME! },
  270. { name: 'DB_PASS', value: process.env.DB_PASS! },
  271. ],
  272. command: ['/bin/sh', '-c'],
  273. args: ['yarn workspace query-node-root db:prepare; yarn workspace query-node-root db:migrate'],
  274. },
  275. ],
  276. restartPolicy: 'Never',
  277. },
  278. },
  279. },
  280. },
  281. { ...resourceOptions, dependsOn: processorDb.service }
  282. )
  283. appLabels = { appClass: 'graphql-server' }
  284. const graphqlDeployment = new k8s.apps.v1.Deployment(
  285. 'graphql-server',
  286. {
  287. metadata: {
  288. namespace: namespaceName,
  289. labels: appLabels,
  290. },
  291. spec: {
  292. replicas: 1,
  293. selector: { matchLabels: appLabels },
  294. template: {
  295. metadata: {
  296. labels: appLabels,
  297. },
  298. spec: {
  299. containers: [
  300. {
  301. name: 'graphql-server',
  302. image: joystreamAppsImage,
  303. imagePullPolicy: 'IfNotPresent',
  304. env: [
  305. { name: 'DB_HOST', value: processorDbName },
  306. { name: 'DB_PASS', value: process.env.DB_PASS! },
  307. { name: 'DB_USER', value: process.env.DB_USER! },
  308. { name: 'DB_PORT', value: process.env.DB_PORT! },
  309. { name: 'DB_NAME', value: process.env.DB_NAME! },
  310. { name: 'GRAPHQL_SERVER_HOST', value: process.env.GRAPHQL_SERVER_HOST! },
  311. { name: 'GRAPHQL_SERVER_PORT', value: process.env.GRAPHQL_SERVER_PORT! },
  312. { name: 'WS_PROVIDER_ENDPOINT_URI', value: process.env.WS_PROVIDER_ENDPOINT_URI! },
  313. ],
  314. ports: [{ name: 'graph-ql-port', containerPort: Number(process.env.GRAPHQL_SERVER_PORT!) }],
  315. args: ['workspace', 'query-node-root', 'query-node:start:prod'],
  316. },
  317. ],
  318. },
  319. },
  320. },
  321. },
  322. { ...resourceOptions, dependsOn: processorMigrationJob }
  323. )
  324. // Create a Service for the GraphQL Server
  325. const graphqlService = new k8s.core.v1.Service(
  326. 'graphql-server',
  327. {
  328. metadata: {
  329. labels: appLabels,
  330. namespace: namespaceName,
  331. name: 'graphql-server',
  332. },
  333. spec: {
  334. ports: [{ name: 'port-1', port: 8081, targetPort: 'graph-ql-port' }],
  335. selector: appLabels,
  336. },
  337. },
  338. resourceOptions
  339. )
  340. const indexerURL = externalIndexerUrl || `http://indexer:4000/graphql`
  341. appLabels = { appClass: 'processor' }
  342. const processorDeployment = new k8s.apps.v1.Deployment(
  343. `processor`,
  344. {
  345. metadata: {
  346. namespace: namespaceName,
  347. labels: appLabels,
  348. },
  349. spec: {
  350. replicas: 1,
  351. selector: { matchLabels: appLabels },
  352. template: {
  353. metadata: {
  354. labels: appLabels,
  355. },
  356. spec: {
  357. containers: [
  358. {
  359. name: 'processor',
  360. image: joystreamAppsImage,
  361. imagePullPolicy: 'IfNotPresent',
  362. env: [
  363. {
  364. name: 'INDEXER_ENDPOINT_URL',
  365. value: indexerURL,
  366. },
  367. { name: 'TYPEORM_HOST', value: processorDbName },
  368. { name: 'TYPEORM_DATABASE', value: process.env.DB_NAME! },
  369. { name: 'DEBUG', value: 'index-builder:*' },
  370. { name: 'PROCESSOR_POLL_INTERVAL', value: '1000' },
  371. ],
  372. volumeMounts: [
  373. {
  374. mountPath: '/joystream/query-node/mappings/lib/generated/types/typedefs.json',
  375. name: 'processor-volume',
  376. subPath: 'fileData',
  377. },
  378. ],
  379. command: ['/bin/sh', '-c'],
  380. args: ['cd query-node && yarn hydra-processor run -e ../.env'],
  381. },
  382. ],
  383. volumes: [
  384. {
  385. name: 'processor-volume',
  386. configMap: {
  387. name: defsConfig,
  388. },
  389. },
  390. ],
  391. },
  392. },
  393. },
  394. },
  395. { ...resourceOptions, dependsOn: graphqlService }
  396. )
  397. }
  398. const caddyEndpoints = [
  399. `/indexer* {
  400. uri strip_prefix /indexer
  401. reverse_proxy indexer:4000
  402. }`,
  403. `/server* {
  404. uri strip_prefix /server
  405. reverse_proxy graphql-server:8081
  406. }`,
  407. ]
  408. const lbReady = config.get('isLoadBalancerReady') === 'true'
  409. export let endpoint1: pulumi.Output<string>
  410. export let endpoint2: pulumi.Output<string>
  411. if (!isMinikube) {
  412. const caddy = new CaddyServiceDeployment(
  413. 'caddy-proxy',
  414. { lbReady, namespaceName: namespaceName, isMinikube, caddyEndpoints },
  415. resourceOptions
  416. )
  417. endpoint1 = pulumi.interpolate`${caddy.primaryEndpoint}`
  418. endpoint2 = pulumi.interpolate`${caddy.secondaryEndpoint}`
  419. }